From 70c2f61f0551e994e1891d6c0fac2293d7008726 Mon Sep 17 00:00:00 2001 From: Marcus van Elst Date: Fri, 17 Apr 2026 22:57:47 +0200 Subject: [PATCH] feat(like_post): add unlike support via remove.0 array dot-notation (v1.2.0) Unlike is now implemented: add=\$empty, remove.0=STAR (verified via Network Interceptor). Adds optional mood parameter (default STAR). Removes the early-return error path for like=False. SPEC.md, CLAUDE.md, CHANGELOG.md updated; Unlike offene Punkte entry removed. Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 2 ++ CLAUDE.md | 7 ++-- SPEC.md | 20 +++++++---- src/mcp_familywall/server.py | 69 ++++++++++++++++-------------------- 4 files changed, 50 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9909967..044726a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ Breaking changes (removed tools, changed parameters) increment the major version ## [1.2.0] – 2026-04-17 ### Added +- **Unlike support** for `like_post`: passing `like=False` now removes a STAR reaction via + `add=$empty, remove.0=STAR` (array dot-notation). New optional `mood` parameter (default `"STAR"`). - **Reminder write support** for `create_task` and `update_task`: - `reminder_unit`: `"MINUTE"`, `"HOUR"`, or `"DAY"` - `reminder_value`: non-negative integer (e.g. `30` for "30 minutes before") diff --git a/CLAUDE.md b/CLAUDE.md index c003f30..2f36d96 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -139,7 +139,7 @@ Fehler bei falschen Parametern kommen nicht immer auf Top-Level: | `taskupdate2` | **⚠️ Encoding** | Recurrency flach top-level. Reminder **nur Dot-Notation** `reminder.*` — flache Keys, JSON-String, Brackets werden silent-ignored. | | `taskmark` | `taskId`, `complete` | `"true"`/`"false"` | | `metadelete` | `id` | metaId des Tasks / Rezepts | -| `wallmood` | `wall_message_id`, `moodType` | `"STAR"` für Like | +| `wallmood` | `wall_message_id`, `add`, `remove`/`remove.0` | Like: `add="STAR", remove="$empty"`; Unlike: `add="$empty", remove.0="STAR"` (Array-Dot-Notation) | | `taskcategoryput` | `name`, `emoji` | – | | `taskcategorydelete` | `id` | metaId der Kategorie | | `taskcreatelist` | `name`, `taskListType`, `sharedToAll`, `color`, `emoji`, `scope` | `taskListType`: `SHOPPING_LIST`, `TODOS`, `OTHER`; `scope`: Kreis-metaId für nicht-primäre Kreise | @@ -158,9 +158,8 @@ Fehler bei falschen Parametern kommen nicht immer auf Top-Level: ### Self-Like-Restriction Eigene Posts können nicht geliked werden. API antwortet 200, macht aber nichts. -### Unlike nicht möglich -Service Worker verschlüsselt den Unlike-Request-Body. -Endpoint unbekannt. `like_post(like=False)` gibt Fehlermeldung zurück. +### Unlike +Unlike via `remove.0=STAR` (Array-Dot-Notation). Verifiziert 2026-04-17 via Network-Interceptor. ### mpstar / Rezept-Favorit Service Worker fängt `mpstar` ab. `metamood` funktioniert nur auf diff --git a/SPEC.md b/SPEC.md index 419f33b..841bdbb 100644 --- a/SPEC.md +++ b/SPEC.md @@ -275,26 +275,35 @@ POST https://api.familywall.com/api/metadelete a00.r.r → "true" (String) ``` -### `wallmood` – Post liken +### `wallmood` – Post liken / unlike POST https://api.familywall.com/api/wallmood -**Body-Parameter:** +**Body-Parameter (Like):** | Parameter | Wert | |---|---| | `wall_message_id` | Post-metaId ⚠️ nicht `wallId` oder `id`! | -| `moodType` | `"STAR"` für Like | +| `add` | `"STAR"` (Mood-Typ setzen) | +| `remove` | `"$empty"` (Sentinel für „nichts entfernen") | + +**Body-Parameter (Unlike):** + +| Parameter | Wert | +|---|---| +| `wall_message_id` | Post-metaId | +| `add` | `"$empty"` (Sentinel für „nichts hinzufügen") | +| `remove.0` | `"STAR"` (Array-Dot-Notation — `remove` ist ein Array) | **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" ``` +**Verifiziert am:** 2026-04-17 via Network-Interceptor (echter Request-Body) + ### `taskcategoryput` – Kategorie erstellen/aktualisieren POST https://api.familywall.com/api/taskcategoryput @@ -949,7 +958,6 @@ bevor er den Server erreicht. Die Transformation ist ohne Service-Worker-Analyse ## Offene Punkte -- Unlike-Endpoint (Service Worker blockiert Analyse) - `mpstar` / `isFavorite` für Rezepte (Service Worker blockiert Analyse, siehe oben) - Erinnerungen (reminder) – nur Premium-Account - Wiederholungen (repeat) – nur Premium-Account diff --git a/src/mcp_familywall/server.py b/src/mcp_familywall/server.py index a5bfd0b..91770b1 100644 --- a/src/mcp_familywall/server.py +++ b/src/mcp_familywall/server.py @@ -1901,43 +1901,33 @@ def delete_circle(circle_id: str) -> str: @mcp.tool() -def like_post(post_id: str, like: bool = True) -> str: - """Like a wall post/activity with a STAR mood. +def like_post(post_id: str, like: bool = True, mood: str = "STAR") -> str: + """Like or unlike a wall post/activity. IMPORTANT: Ask the user for confirmation before calling this tool. - 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: Must be ``True``. ``False`` is reserved for future unlike support. + like: ``True`` to add a like (default), ``False`` to remove it. + mood: Mood type to set or remove. Default is ``"STAR"``. Returns: - JSON success indicator or an error message. + JSON with the resulting like state or an error message. """ - # 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; "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", - } + # Like: add=STAR, remove=$empty (idempotent set) + # Unlike: add=$empty, remove.0=STAR (array dot-notation, verified via Network Interceptor) + if like: + params: dict[str, Any] = { + "wall_message_id": post_id, + "add": mood, + "remove": "$empty", + } + else: + params = { + "wall_message_id": post_id, + "add": "$empty", + "remove.0": mood, + } try: data = _authenticated_call("wallmood", params) @@ -1968,15 +1958,18 @@ def like_post(post_id: str, like: bool = True) -> str: result: dict[str, Any] = {"liked": now_liked, "id": post_id, "author": account_id} - # Surface a warning when the like call apparently had no effect, so the - # caller can distinguish a successful like from a silent API rejection - # (e.g. rate limit, unsupported post type, or self-like restriction). - if not now_liked: - result["warning"] = ( - "Like may not have been applied. " - "Possible causes: rate limit, unsupported post type (e.g. FAMILY_CREATED), " - "or self-like restriction." - ) + if now_liked != like: + if like: + result["warning"] = ( + "Like may not have been applied. " + "Possible causes: rate limit, unsupported post type (e.g. FAMILY_CREATED), " + "or self-like restriction." + ) + else: + result["warning"] = ( + "Unlike may not have been applied. " + "Possible causes: post was not liked, rate limit, or API restriction." + ) return json.dumps(result, ensure_ascii=False, indent=2)