fix: extract list IDs from sortingIndexByTaskList, remove tasklistsync (v0.2.2)

This commit is contained in:
2026-04-15 13:38:52 +02:00
parent 8262c8440c
commit 8cf707e3bd
4 changed files with 52 additions and 33 deletions
+11 -12
View File
@@ -97,26 +97,23 @@ Content-Type: application/x-www-form-urlencoded
|---|---| |---|---|
| `a01call` | `"taskcategorysync"` | | `a01call` | `"taskcategorysync"` |
| `a02call` | `"tasksync"` | | `a02call` | `"tasksync"` |
| `a03call` | `"tasklistsync"` |
Hinweis: `partnerScope`, `a03id`, `withStateBean` werden weggelassen. 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):** **Response-Struktur (verifiziert):**
``` ```
a00 → famlistfamily-Daten (Kreise) Nebeneffekt, nicht verwendet a00 → famlistfamily-Daten (Kreise) Nebeneffekt, nicht verwendet
a01.r.r[] → taskcategorysync (Einkaufskategorien/Abteilungen) 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) a02.r.r.updatedCreated[] → tasksync (Tasks)
.metaId → eindeutige Task-ID .metaId → eindeutige Task-ID
.text → Aufgabentext .text → Aufgabentext
.description → optionale Beschreibung .description → optionale Beschreibung
.taskListId → Zugehörigkeit zur Liste (= metaId der Liste) .taskListId → Zugehörigkeit zur Liste (= Listen-ID aus sortingIndexByTaskList)
.complete → "true" / "false" (String, nicht Boolean!) .complete → "true" / "false" (String, nicht Boolean!)
a03.r.r.updatedCreated[] → tasklistsync (Listen)
.metaId → eindeutige Listen-ID
.name → Name (ggf. Systembezeichnung, s.u.)
.taskListType → Typ der Liste
.remainingTaskNumber → offene Einträge (String)
.totalTaskNumber → Gesamteinträge (String)
``` ```
## Systembezeichnungen für Listen-Namen ## Systembezeichnungen für Listen-Namen
@@ -142,7 +139,9 @@ offener Punkte (z.B. `type`-Parameter beim Login, Kreis-Felder in Response).
- ~~Exakter Wert für `type`-Parameter beim Login~~ → nicht senden (verifiziert per JS-Analyse) - ~~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) - ~~Response-Struktur von `famlistfamily` (Kreise)~~ → a00.r.r[], metaId + name (verifiziert)
- ~~Ob `a03call=tasklistsync` benötigt wird~~ → ja, liefert Listen unter a03.r.r.updatedCreated[] (verifiziert) - ~~Ob `a03call=tasklistsync` benötigt wird~~ → **nein**, kein gültiger Endpoint (verifiziert)
- Kreis-Zuordnung in `accgetallfamily`-Response → noch offen (Feld in Listen-Objekten unbekannt) - 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) - ~~Ob `partnerScope` / `withStateBean` benötigt werden~~ → nein (verifiziert)
- Session-Lebensdauer (irrelevant da kein Caching) - Session-Lebensdauer (irrelevant da kein Caching)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "mcp-familywall" name = "mcp-familywall"
version = "0.2.1" version = "0.2.2"
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
View File
@@ -1 +1 @@
__version__ = "0.2.1" __version__ = "0.2.2"
+33 -13
View File
@@ -37,11 +37,7 @@ def _accgetallfamily() -> dict[str, Any]:
client.login(email, password) client.login(email, password)
data = client.call( data = client.call(
"accgetallfamily", "accgetallfamily",
{ {"a01call": "taskcategorysync", "a02call": "tasksync"},
"a01call": "taskcategorysync",
"a02call": "tasksync",
"a03call": "tasklistsync",
},
) )
client.logout() client.logout()
return data return data
@@ -54,22 +50,46 @@ def _accgetallfamily() -> dict[str, Any]:
def _extract_lists(data: dict[str, Any]) -> list[dict[str, Any]]: def _extract_lists(data: dict[str, Any]) -> list[dict[str, Any]]:
"""Extract task lists from an accgetallfamily response. """Extract task lists from an accgetallfamily response.
Lists live under a03.r.r.updatedCreated[] (tasklistsync). List IDs are derived from the sortingIndexByTaskList keys present in each
taskcategorysync entry (a01.r.r.updatedCreated[]). Each key is a unique
list ID of the form ``taskList/<id>``. Names and counters are not yet
available from this path and are left as None.
Args: Args:
data: Raw response body from accgetallfamily. data: Raw response body from accgetallfamily.
Returns: Returns:
List of raw task-list dicts (may be empty). Deduplicated list of dicts with keys id, name, type, open, total.
""" """
try: try:
items = data["a03"]["r"]["r"]["updatedCreated"] categories = data["a01"]["r"]["r"]["updatedCreated"]
if isinstance(items, list): if not isinstance(categories, list):
logger.debug("Lists found under a03.r.r.updatedCreated (%d items)", len(items))
return items # type: ignore[return-value]
except (KeyError, TypeError):
pass
return [] return []
except (KeyError, TypeError):
return []
seen: set[str] = set()
result: list[dict[str, Any]] = []
for cat in categories:
sorting = cat.get("sortingIndexByTaskList")
if not isinstance(sorting, dict):
continue
for list_id in sorting:
if list_id in seen:
continue
seen.add(list_id)
result.append(
{
"id": list_id,
"name": list_id, # real name unknown — TODO once field identified
"type": "UNKNOWN",
"open": None,
"total": None,
}
)
logger.debug("Extracted %d unique list IDs from sortingIndexByTaskList", len(result))
return result
def _extract_tasks(data: dict[str, Any]) -> list[dict[str, Any]]: def _extract_tasks(data: dict[str, Any]) -> list[dict[str, Any]]: