fix(lists): circle scope support for get_lists, create_list, delete_list (v0.7.1)
- get_lists(scope): API scope parameter now used server-side; accepts circle
metaId ("family/XXXX") or circle name; returns circle_id field per list
- create_list(circle_id): new optional param; passes as API scope param
- delete_list: derives circle from list metaId and passes scope for
secondary-circle lists
- Added _circle_id_from_list_id() helper (taskList/FAMNUM_LISTNUM -> family/FAMNUM)
- SPEC.md: documented scope param for taskgettasklists, taskcreatelist, taskdeletelist
- Verified: familyId/circleId/id params ignored by API, only scope works
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,7 @@ und wird in Claude Desktop eingebunden.
|
|||||||
|
|
||||||
## Aktueller Stand
|
## Aktueller Stand
|
||||||
|
|
||||||
### Implementierte Tools (v0.7.0)
|
### Implementierte Tools (v0.7.1)
|
||||||
|
|
||||||
| Kategorie | Tools |
|
| Kategorie | Tools |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -45,8 +45,9 @@ und wird in Claude Desktop eingebunden.
|
|||||||
- v0.5.3: Kategorie-Auto-Assign-Hinweis im create_task Docstring ✓ (nachgeliefert in v0.6.1)
|
- v0.5.3: Kategorie-Auto-Assign-Hinweis im create_task Docstring ✓ (nachgeliefert in v0.6.1)
|
||||||
- v0.6.0: Rezept-Box (get_recipes, get_recipe, create_recipe, delete_recipe) ✓
|
- v0.6.0: Rezept-Box (get_recipes, get_recipe, create_recipe, delete_recipe) ✓
|
||||||
- v0.6.1: update_recipe + Bugfix Zeilenumbrüche in create_recipe ✓
|
- v0.6.1: update_recipe + Bugfix Zeilenumbrüche in create_recipe ✓
|
||||||
- v0.7.0: create_circle + add_member_to_circle ✓ ← aktuell
|
- v0.7.0: create_circle + add_member_to_circle ✓
|
||||||
- v0.7.1: mpadditemtolist (Zutaten → Einkaufsliste)
|
- v0.7.1: get_lists scope fix + create_list circle_id + delete_list scope ✓ ← aktuell
|
||||||
|
- v0.7.2: mpadditemtolist (Zutaten → Einkaufsliste)
|
||||||
- v0.5.3: update_list (Umbenennen, emoji/color ändern), Sharing-Verwaltung
|
- v0.5.3: update_list (Umbenennen, emoji/color ändern), Sharing-Verwaltung
|
||||||
- v0.8.x: Erinnerungen + Wiederholungen (Premium-Account erforderlich)
|
- v0.8.x: Erinnerungen + Wiederholungen (Premium-Account erforderlich)
|
||||||
- v2.0: Schreibzugriff auf Wall-Posts (Erstellen, Kommentieren)
|
- v2.0: Schreibzugriff auf Wall-Posts (Erstellen, Kommentieren)
|
||||||
@@ -62,8 +63,12 @@ um unnötige HTTP-Roundtrips zu vermeiden. Credentials liegen im OS Keyring
|
|||||||
|
|
||||||
### Kreise (Scopes)
|
### Kreise (Scopes)
|
||||||
Family Wall kennt mehrere Kreise (z.B. Familie, erweiterter Familienkreis).
|
Family Wall kennt mehrere Kreise (z.B. Familie, erweiterter Familienkreis).
|
||||||
`get_lists` unterstützt optionalen `scope`-Parameter zur Filterung.
|
Der API-Parameter `scope=family/XXXX` schaltet den Server-Kontext um.
|
||||||
Ohne `scope` werden alle Kreise zurückgegeben.
|
- `taskgettasklists` ohne scope → primärer Kreis; mit scope → angegebener Kreis
|
||||||
|
- `taskcreatelist` mit scope → neue Liste im angegebenen Kreis
|
||||||
|
- `taskdeletelist` mit scope → löscht Liste aus angegebenem Kreis
|
||||||
|
- `get_lists(scope="family/XXXX")` oder `get_lists(scope="Kreis-Name")` zur Filterung
|
||||||
|
- Die Listen-metaId kodiert den Kreis: `taskList/<FAMNUM>_<LISTNUM>` → `family/<FAMNUM>`
|
||||||
|
|
||||||
### Listen-Namen
|
### Listen-Namen
|
||||||
Systembezeichnungen (z.B. `SYS-CAT-SHOPPINGLIST`) werden in deutsche
|
Systembezeichnungen (z.B. `SYS-CAT-SHOPPINGLIST`) werden in deutsche
|
||||||
@@ -133,8 +138,9 @@ Fehler bei falschen Parametern kommen nicht immer auf Top-Level:
|
|||||||
| `wallmood` | `wall_message_id`, `moodType` | `"STAR"` für Like |
|
| `wallmood` | `wall_message_id`, `moodType` | `"STAR"` für Like |
|
||||||
| `taskcategoryput` | `name`, `emoji` | – |
|
| `taskcategoryput` | `name`, `emoji` | – |
|
||||||
| `taskcategorydelete` | `id` | metaId der Kategorie |
|
| `taskcategorydelete` | `id` | metaId der Kategorie |
|
||||||
| `taskcreatelist` | `name`, `taskListType`, `sharedToAll`, `color`, `emoji` | `taskListType`: `"SHOPPING_LIST"`/`"TODOS"` |
|
| `taskcreatelist` | `name`, `taskListType`, `sharedToAll`, `color`, `emoji`, `scope` | `scope`: Kreis-metaId für nicht-primäre Kreise |
|
||||||
| `taskdeletelist` | `id` | metaId der Liste |
|
| `taskgettasklists` | `scope` | Kreis-metaId; ohne scope → primärer Kreis |
|
||||||
|
| `taskdeletelist` | `id`, `scope` | `scope`: Kreis-metaId für sekundäre Kreise |
|
||||||
| `mprecipeput` | `recipe.name`, `recipe.isRecipe="true"`, `recipe.description`, `recipe.ingredients`, `recipe.instructions`, `recipe.prepTime`, `recipe.cookTime`, `recipe.serves`, `recipe.url` | Alle mit `recipe.`-Prefix! |
|
| `mprecipeput` | `recipe.name`, `recipe.isRecipe="true"`, `recipe.description`, `recipe.ingredients`, `recipe.instructions`, `recipe.prepTime`, `recipe.cookTime`, `recipe.serves`, `recipe.url` | Alle mit `recipe.`-Prefix! |
|
||||||
| `mprecipeput` (Update) | zusätzlich `recipe.metaId` | Vorhandene ID → Update statt Create |
|
| `mprecipeput` (Update) | zusätzlich `recipe.metaId` | Vorhandene ID → Update statt Create |
|
||||||
| `metasync` (Rezepte lesen) | `id="recipe"` | liefert `a00.r.r.updatedCreated[]` |
|
| `metasync` (Rezepte lesen) | `id="recipe"` | liefert `a00.r.r.updatedCreated[]` |
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
MCP server for [Family Wall](https://www.familywall.com) -- read and manage your family's circles, lists, tasks, and recipes directly from Claude.
|
MCP server for [Family Wall](https://www.familywall.com) -- read and manage your family's circles, lists, tasks, and recipes directly from Claude.
|
||||||
|
|
||||||
## Features (v0.7.0)
|
## Features (v0.7.1)
|
||||||
|
|
||||||
### Read
|
### Read
|
||||||
|
|
||||||
- `get_circles` -- list all family circles
|
- `get_circles` -- list all family circles
|
||||||
- `get_members` -- list members of a circle (or all circles)
|
- `get_members` -- list members of a circle (or all circles)
|
||||||
- `get_lists` -- list all task lists (includes `emoji` and `color`; `null` when unset)
|
- `get_lists` -- list all task lists (includes `emoji`, `color`, `circle_id`; `null` when unset); optional `scope` parameter filters by circle metaId or circle name
|
||||||
- `get_tasks` -- list tasks in a specific list (includes `category_id`, `due_date`, `assignee_ids`)
|
- `get_tasks` -- list tasks in a specific list (includes `category_id`, `due_date`, `assignee_ids`)
|
||||||
- `get_categories` -- list categories for a list (locale-filtered; custom categories always included; `custom` flag marks user-created ones)
|
- `get_categories` -- list categories for a list (locale-filtered; custom categories always included; `custom` flag marks user-created ones)
|
||||||
- `get_activities` -- list recent wall activities (author resolved to display name)
|
- `get_activities` -- list recent wall activities (author resolved to display name)
|
||||||
@@ -21,7 +21,7 @@ MCP server for [Family Wall](https://www.familywall.com) -- read and manage your
|
|||||||
- `update_task` -- update text, description, category, due date, assignees, or move to a different list; supports `clear_due_date=True` to remove a due date
|
- `update_task` -- update text, description, category, due date, assignees, or move to a different list; supports `clear_due_date=True` to remove a due date
|
||||||
- `toggle_task` -- mark a task complete or reopen it
|
- `toggle_task` -- mark a task complete or reopen it
|
||||||
- `delete_task` -- permanently delete a task
|
- `delete_task` -- permanently delete a task
|
||||||
- `create_list` -- create a new task list (SHOPPING_LIST or TODOS; optional `emoji` and `color`)
|
- `create_list` -- create a new task list (SHOPPING_LIST or TODOS; optional `emoji`, `color`, and `circle_id` to target a specific circle)
|
||||||
- `delete_list` -- permanently delete a list and all its tasks (system lists are protected)
|
- `delete_list` -- permanently delete a list and all its tasks (system lists are protected)
|
||||||
- `create_category` -- create a custom category for a shopping list (with optional icon)
|
- `create_category` -- create a custom category for a shopping list (with optional icon)
|
||||||
- `delete_category` -- delete a custom category (system categories are protected)
|
- `delete_category` -- delete a custom category (system categories are protected)
|
||||||
|
|||||||
@@ -292,6 +292,13 @@ POST https://api.familywall.com/api/taskcreatelist
|
|||||||
| `sharedToAll` | nein | `"true"` / `"false"` (default: `"true"`) |
|
| `sharedToAll` | nein | `"true"` / `"false"` (default: `"true"`) |
|
||||||
| `color` | nein | Hex-Farbwert z.B. `"#4784EC"` |
|
| `color` | nein | Hex-Farbwert z.B. `"#4784EC"` |
|
||||||
| `emoji` | nein | Unicode-Emoji z.B. `"🛒"` |
|
| `emoji` | nein | Unicode-Emoji z.B. `"🛒"` |
|
||||||
|
| `scope` | nein | Kreis-metaId z.B. `"family/23447378"` (ohne scope → primärer Kreis) |
|
||||||
|
|
||||||
|
**Scope-Verhalten:**
|
||||||
|
- Ohne `scope`: Liste wird im primären Kreis des Accounts erstellt
|
||||||
|
- Mit `scope=family/XXXX`: Liste wird im angegebenen Kreis erstellt
|
||||||
|
- Die `metaId` der neuen Liste kodiert den Kreis: `taskList/CIRCLENUM_LISTNUM`
|
||||||
|
- Parameter `familyId`, `circleId`, `family`, `id` → werden ignoriert, nur `scope` wirkt
|
||||||
|
|
||||||
**Response:**
|
**Response:**
|
||||||
```
|
```
|
||||||
@@ -302,6 +309,7 @@ a00.r.r → vollständiges Listen-Objekt
|
|||||||
.sharedToAll → "true" / "false"
|
.sharedToAll → "true" / "false"
|
||||||
.emoji → Unicode-Emoji (fehlt wenn nicht gesetzt)
|
.emoji → Unicode-Emoji (fehlt wenn nicht gesetzt)
|
||||||
.color → Hex-Farbwert z.B. "#E53935" (fehlt wenn nicht gesetzt)
|
.color → Hex-Farbwert z.B. "#E53935" (fehlt wenn nicht gesetzt)
|
||||||
|
.familyId → Kreis-metaId des erstellten Liste
|
||||||
.rights.canDelete → "true" (user-created lists)
|
.rights.canDelete → "true" (user-created lists)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -312,9 +320,10 @@ POST https://api.familywall.com/api/taskdeletelist
|
|||||||
|
|
||||||
**Body-Parameter:**
|
**Body-Parameter:**
|
||||||
|
|
||||||
| Parameter | Wert |
|
| Parameter | Pflicht | Wert |
|
||||||
|---|---|
|
|---|---|---|
|
||||||
| `id` | Listen-metaId ⚠️ nicht `listId` oder `taskListId`! |
|
| `id` | ja | Listen-metaId ⚠️ nicht `listId` oder `taskListId`! |
|
||||||
|
| `scope` | nein | Kreis-metaId (erforderlich für sekundäre Kreise) |
|
||||||
|
|
||||||
**Response:**
|
**Response:**
|
||||||
```
|
```
|
||||||
@@ -324,20 +333,32 @@ a00.r.r → "true" (String)
|
|||||||
**Hinweis:** Löscht die Liste und alle enthaltenen Tasks unwiderruflich.
|
**Hinweis:** Löscht die Liste und alle enthaltenen Tasks unwiderruflich.
|
||||||
System-Listen (`rights.canDelete` fehlt oder `null`) sind nicht löschbar.
|
System-Listen (`rights.canDelete` fehlt oder `null`) sind nicht löschbar.
|
||||||
MCP-Server prüft dies vor dem Löschen via `taskgettasklists`.
|
MCP-Server prüft dies vor dem Löschen via `taskgettasklists`.
|
||||||
|
Für Listen in sekundären Kreisen muss `scope=family/XXXX` mitgeschickt werden.
|
||||||
|
|
||||||
**Verifiziert am:** 2026-04-16 via FW_DEBUG=1
|
**Verifiziert am:** 2026-04-16 via FW_DEBUG=1
|
||||||
|
|
||||||
### `taskgettasklists` – Listen abrufen
|
### `taskgettasklists` – Listen abrufen
|
||||||
POST https://api.familywall.com/api/taskgettasklists
|
POST https://api.familywall.com/api/taskgettasklists
|
||||||
|
|
||||||
**Body-Parameter:** keine
|
**Body-Parameter:**
|
||||||
|
|
||||||
|
| Parameter | Pflicht | Wert |
|
||||||
|
|---|---|---|
|
||||||
|
| `scope` | nein | Kreis-metaId z.B. `"family/23447378"` (ohne scope → primärer Kreis) |
|
||||||
|
|
||||||
|
**Scope-Verhalten:**
|
||||||
|
- Ohne `scope`: Nur Listen des primären Kreises werden zurückgegeben
|
||||||
|
- Mit `scope=family/XXXX`: Nur Listen des angegebenen Kreises
|
||||||
|
- Es gibt keinen servereitigen Filter für mehrere Kreise gleichzeitig
|
||||||
|
- Andere Parameter (`familyId`, `circleId`, etc.) werden ignoriert
|
||||||
|
|
||||||
**Response-Struktur:**
|
**Response-Struktur:**
|
||||||
```
|
```
|
||||||
a00.r.r[] → Liste aller Task-Listen
|
a00.r.r[] → Liste aller Task-Listen des Kreises
|
||||||
.metaId → Listen-ID (z.B. "taskList/23431854_29740942")
|
.metaId → Listen-ID (z.B. "taskList/23431854_29740942")
|
||||||
.name → Systembezeichnung oder Benutzer-Name
|
.name → Systembezeichnung oder Benutzer-Name
|
||||||
.taskListType → SHOPPING_LIST oder TODOS
|
.taskListType → SHOPPING_LIST oder TODOS
|
||||||
|
.familyId → Kreis-metaId (z.B. "family/23431854")
|
||||||
.emoji → Unicode-Emoji oder "" (leerer String = kein Emoji)
|
.emoji → Unicode-Emoji oder "" (leerer String = kein Emoji)
|
||||||
.color → Hex-Farbwert z.B. "#E53935" (fehlt wenn nicht gesetzt)
|
.color → Hex-Farbwert z.B. "#E53935" (fehlt wenn nicht gesetzt)
|
||||||
.remainingTaskNumber → offene Tasks (String)
|
.remainingTaskNumber → offene Tasks (String)
|
||||||
@@ -348,6 +369,9 @@ a00.r.r[] → Liste aller Task-Listen
|
|||||||
.systemId → vorhanden nur bei Systemlisten (z.B. "-10", "-11")
|
.systemId → vorhanden nur bei Systemlisten (z.B. "-10", "-11")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**metaId-Encoding:** `taskList/<family_num>_<list_num>` — die `family_num` kodiert den Kreis.
|
||||||
|
Beispiel: `taskList/23431854_29740942` gehört zu `family/23431854`.
|
||||||
|
|
||||||
**Hinweis emoji/color:**
|
**Hinweis emoji/color:**
|
||||||
- `emoji`: Systemlisten liefern `""`, user-created Listen liefern den Emoji-String
|
- `emoji`: Systemlisten liefern `""`, user-created Listen liefern den Emoji-String
|
||||||
oder `""` wenn kein Emoji gesetzt. Normalisierung: `""` → `null` im MCP-Server.
|
oder `""` wenn kein Emoji gesetzt. Normalisierung: `""` → `null` im MCP-Server.
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "mcp-familywall"
|
name = "mcp-familywall"
|
||||||
version = "0.7.0"
|
version = "0.7.1"
|
||||||
description = "MCP server for Family Wall — read your family's lists and tasks via Claude"
|
description = "MCP server for Family Wall — read your family's lists and tasks via Claude"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "0.7.0"
|
__version__ = "0.7.1"
|
||||||
|
|||||||
@@ -141,6 +141,26 @@ def get_circles():
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def _circle_id_from_list_id(list_id: str) -> str | None:
|
||||||
|
"""Derive the circle metaId from a list metaId.
|
||||||
|
|
||||||
|
The list metaId format is ``taskList/<family_num>_<list_num>``.
|
||||||
|
This function returns ``"family/<family_num>"``.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
list_id: List metaId (e.g. ``"taskList/23431854_29759623"``).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Circle metaId (e.g. ``"family/23431854"``), or ``None`` when the
|
||||||
|
format cannot be parsed.
|
||||||
|
"""
|
||||||
|
bare = list_id.removeprefix("taskList/")
|
||||||
|
parts = bare.split("_", 1)
|
||||||
|
if len(parts) == 2 and parts[0].isdigit():
|
||||||
|
return f"family/{parts[0]}"
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _famlistfamily() -> list[dict[str, Any]]:
|
def _famlistfamily() -> list[dict[str, Any]]:
|
||||||
"""Login, call famlistfamily, logout and return the raw circle list.
|
"""Login, call famlistfamily, logout and return the raw circle list.
|
||||||
|
|
||||||
@@ -252,17 +272,64 @@ def get_members(circle_id: str | None = None) -> str:
|
|||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
def get_lists(scope: str | None = None):
|
def get_lists(scope: str | None = None) -> str:
|
||||||
"""Return task lists as JSON, optionally filtered by circle name (scope)."""
|
"""Return task lists as JSON, optionally filtered to a specific circle.
|
||||||
|
|
||||||
|
Each list object includes a ``circle_id`` field with the owning circle's
|
||||||
|
metaId (e.g. ``"family/23431854"``).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
scope: Optional circle filter. Accepts either:
|
||||||
|
|
||||||
|
- A circle metaId (e.g. ``"family/23447378"``) — passed directly
|
||||||
|
to the API.
|
||||||
|
- A circle display name (e.g. ``"Test Kreis 2"``) — resolved to
|
||||||
|
the matching circle metaId via ``get_circles`` first.
|
||||||
|
|
||||||
|
When ``None`` (default) the primary circle's lists are returned.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON list of list objects with keys id, name, type, open, total,
|
||||||
|
emoji, color, circle_id.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
email, password = get_credentials()
|
email, password = get_credentials()
|
||||||
except RuntimeError as exc:
|
except RuntimeError as exc:
|
||||||
return f"Error: {exc}"
|
return f"Error: {exc}"
|
||||||
|
|
||||||
|
# Build the scope param for taskgettasklists.
|
||||||
|
# When scope is provided as a circle name (not a metaId), we need to
|
||||||
|
# resolve it via famlistfamily first — done in the same session.
|
||||||
|
api_scope: str | None = None
|
||||||
|
if scope:
|
||||||
|
if scope.startswith("family/"):
|
||||||
|
api_scope = scope
|
||||||
|
else:
|
||||||
|
# Treat as circle name — look up the metaId.
|
||||||
|
try:
|
||||||
|
circles = _famlistfamily()
|
||||||
|
except RuntimeError as exc:
|
||||||
|
return f"Error: {exc}"
|
||||||
|
matched = next((c for c in circles if c.get("name") == scope), None)
|
||||||
|
if matched is None:
|
||||||
|
circle_names = [c.get("name") for c in circles]
|
||||||
|
return json.dumps(
|
||||||
|
{
|
||||||
|
"error": f"Circle not found: {scope!r}",
|
||||||
|
"available_circles": circle_names,
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
indent=2,
|
||||||
|
)
|
||||||
|
api_scope = matched["metaId"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with FamilyWallClient() as client:
|
with FamilyWallClient() as client:
|
||||||
client.login(email, password)
|
client.login(email, password)
|
||||||
data = client.call("taskgettasklists", {})
|
params: dict[str, Any] = {}
|
||||||
|
if api_scope:
|
||||||
|
params["scope"] = api_scope
|
||||||
|
data = client.call("taskgettasklists", params)
|
||||||
client.logout()
|
client.logout()
|
||||||
except FamilyWallError as exc:
|
except FamilyWallError as exc:
|
||||||
return f"Error: {exc}"
|
return f"Error: {exc}"
|
||||||
@@ -281,7 +348,6 @@ def get_lists(scope: str | None = None):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if raw_lists is None:
|
if raw_lists is None:
|
||||||
# Response structure not yet verified — return raw JSON for inspection.
|
|
||||||
return json.dumps(
|
return json.dumps(
|
||||||
{"warning": "Unexpected taskgettasklists response structure", "raw": data},
|
{"warning": "Unexpected taskgettasklists response structure", "raw": data},
|
||||||
ensure_ascii=False,
|
ensure_ascii=False,
|
||||||
@@ -290,7 +356,6 @@ def get_lists(scope: str | None = None):
|
|||||||
|
|
||||||
result = []
|
result = []
|
||||||
for item in raw_lists:
|
for item in raw_lists:
|
||||||
# TODO: apply scope filtering once the circle field is identified.
|
|
||||||
# emoji: API returns "" when unset — normalise to None for a clean JSON null.
|
# emoji: API returns "" when unset — normalise to None for a clean JSON null.
|
||||||
# color: API omits the key entirely when unset — .get() returns None directly.
|
# color: API omits the key entirely when unset — .get() returns None directly.
|
||||||
raw_emoji: str = item.get("emoji", "")
|
raw_emoji: str = item.get("emoji", "")
|
||||||
@@ -303,6 +368,7 @@ def get_lists(scope: str | None = None):
|
|||||||
"total": item.get("totalTaskNumber"),
|
"total": item.get("totalTaskNumber"),
|
||||||
"emoji": raw_emoji if raw_emoji else None,
|
"emoji": raw_emoji if raw_emoji else None,
|
||||||
"color": item.get("color") or None,
|
"color": item.get("color") or None,
|
||||||
|
"circle_id": item.get("familyId"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -912,6 +978,7 @@ def create_list(
|
|||||||
shared_to_all: bool = True,
|
shared_to_all: bool = True,
|
||||||
color: str | None = None,
|
color: str | None = None,
|
||||||
emoji: str | None = None,
|
emoji: str | None = None,
|
||||||
|
circle_id: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Create a new task list in Family Wall.
|
"""Create a new task list in Family Wall.
|
||||||
|
|
||||||
@@ -924,9 +991,15 @@ def create_list(
|
|||||||
circle members. When ``False`` it is private to the creator.
|
circle members. When ``False`` it is private to the creator.
|
||||||
color: Optional background colour as a hex string (e.g. ``"#4784EC"``).
|
color: Optional background colour as a hex string (e.g. ``"#4784EC"``).
|
||||||
emoji: Optional Unicode emoji to use as the list icon (e.g. ``"🛒"``).
|
emoji: Optional Unicode emoji to use as the list icon (e.g. ``"🛒"``).
|
||||||
|
circle_id: Optional circle metaId to create the list in
|
||||||
|
(e.g. ``"family/23447378"``). When ``None`` (default) the list
|
||||||
|
is created in the primary circle. Use ``get_circles`` to
|
||||||
|
retrieve available circle IDs.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
JSON with the new list object on success, or an error message.
|
JSON with the new list object on success, or an error message.
|
||||||
|
Includes ``circle_id`` field showing which circle the list was
|
||||||
|
created in.
|
||||||
"""
|
"""
|
||||||
if list_type not in ("SHOPPING_LIST", "TODOS"):
|
if list_type not in ("SHOPPING_LIST", "TODOS"):
|
||||||
return "Error: list_type must be 'SHOPPING_LIST' or 'TODOS'."
|
return "Error: list_type must be 'SHOPPING_LIST' or 'TODOS'."
|
||||||
@@ -942,6 +1015,9 @@ def create_list(
|
|||||||
params["color"] = color
|
params["color"] = color
|
||||||
if emoji:
|
if emoji:
|
||||||
params["emoji"] = emoji
|
params["emoji"] = emoji
|
||||||
|
if circle_id:
|
||||||
|
# The API uses the 'scope' parameter to specify the target circle.
|
||||||
|
params["scope"] = circle_id
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = _authenticated_call("taskcreatelist", params)
|
data = _authenticated_call("taskcreatelist", params)
|
||||||
@@ -968,6 +1044,7 @@ def create_list(
|
|||||||
"shared_to_all": list_obj.get("sharedToAll") == "true",
|
"shared_to_all": list_obj.get("sharedToAll") == "true",
|
||||||
"emoji": raw_emoji if raw_emoji else None,
|
"emoji": raw_emoji if raw_emoji else None,
|
||||||
"color": list_obj.get("color") or None,
|
"color": list_obj.get("color") or None,
|
||||||
|
"circle_id": list_obj.get("familyId") or circle_id,
|
||||||
},
|
},
|
||||||
ensure_ascii=False,
|
ensure_ascii=False,
|
||||||
indent=2,
|
indent=2,
|
||||||
@@ -1003,13 +1080,21 @@ def delete_list(list_id: str) -> str:
|
|||||||
except RuntimeError as exc:
|
except RuntimeError as exc:
|
||||||
return f"Error: {exc}"
|
return f"Error: {exc}"
|
||||||
|
|
||||||
|
# Derive the owning circle from the list metaId so that secondary-circle
|
||||||
|
# lists can be queried and deleted with the correct scope parameter.
|
||||||
|
# Format: taskList/<family_num>_<list_num> → family/<family_num>
|
||||||
|
circle_scope = _circle_id_from_list_id(list_id)
|
||||||
|
|
||||||
list_obj: dict[str, Any] | None = None
|
list_obj: dict[str, Any] | None = None
|
||||||
try:
|
try:
|
||||||
with FamilyWallClient() as client:
|
with FamilyWallClient() as client:
|
||||||
client.login(email, password)
|
client.login(email, password)
|
||||||
|
|
||||||
# Fetch lists and verify the target can be deleted.
|
# Fetch lists scoped to the correct circle and verify deletion rights.
|
||||||
raw = client.call("taskgettasklists", {})
|
get_params: dict[str, Any] = {}
|
||||||
|
if circle_scope:
|
||||||
|
get_params["scope"] = circle_scope
|
||||||
|
raw = client.call("taskgettasklists", get_params)
|
||||||
try:
|
try:
|
||||||
raw_lists: list[dict[str, Any]] = raw["a00"]["r"]["r"]
|
raw_lists: list[dict[str, Any]] = raw["a00"]["r"]["r"]
|
||||||
if not isinstance(raw_lists, list):
|
if not isinstance(raw_lists, list):
|
||||||
@@ -1037,8 +1122,11 @@ def delete_list(list_id: str) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Verified — delete in the same session.
|
# Verified — delete in the same session.
|
||||||
# taskdeletelist uses 'id' (same pattern as metadelete / taskcategorydelete).
|
# For secondary circles the 'scope' parameter is required.
|
||||||
client.call("taskdeletelist", {"id": list_id})
|
del_params: dict[str, Any] = {"id": list_id}
|
||||||
|
if circle_scope:
|
||||||
|
del_params["scope"] = circle_scope
|
||||||
|
client.call("taskdeletelist", del_params)
|
||||||
client.logout()
|
client.logout()
|
||||||
except FamilyWallError as exc:
|
except FamilyWallError as exc:
|
||||||
return f"Error: Family Wall API error: {exc}"
|
return f"Error: Family Wall API error: {exc}"
|
||||||
|
|||||||
Reference in New Issue
Block a user