"""Discover the category assignment parameter name for taskcreate2 / taskupdate2. Usage: FW_DEBUG=1 python scripts/find_category_param.py The script: 1. Fetches the first available shopping-list category (German locale). 2. For each candidate parameter name, creates a test task with that parameter set, checks whether the category appears in the response, then deletes the task. 3. Reports which parameter name (if any) caused the category to be applied. Prerequisites: credentials stored in OS keyring or FW_EMAIL / FW_PASSWORD set. """ from __future__ import annotations import os import sys # Allow running from repo root without installing the package. sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) from mcp_familywall.auth import get_credentials from mcp_familywall.fw_client import FamilyWallClient, FamilyWallError # --------------------------------------------------------------------------- # Config # --------------------------------------------------------------------------- # Shopping list to use for the test (adjust if needed). TEST_LIST_ID = "taskList/23431854_29740941" # Category to assign — will be resolved from the first taskcategorysync entry # for this list in German locale; override here if desired. FORCED_CATEGORY_META_ID: str | None = None # Parameter-name candidates not yet tested (already ruled out are commented out): CANDIDATES: list[dict[str, str | None]] = [ # key → value format description # Full metaId variants {"key": "systemCategoryId", "fmt": "numeric"}, # just the number, e.g. "200" {"key": "taskCategorySystemId", "fmt": "numeric"}, {"key": "categories", "fmt": "meta_id"}, # full taskCategory/… metaId {"key": "categoryIds", "fmt": "meta_id"}, {"key": "taskCategoryName", "fmt": "name"}, # category name as string {"key": "categoryName", "fmt": "name"}, {"key": "taskCategoryId", "fmt": "meta_id"}, # already tried but try numeric too # Numeric variants of already-tried names {"key": "taskCategoryId", "fmt": "numeric"}, {"key": "categoryId", "fmt": "numeric"}, {"key": "category", "fmt": "numeric"}, {"key": "categoryMetaId", "fmt": "numeric"}, ] def _resolve_category(client: FamilyWallClient, list_id: str) -> tuple[str, str, str]: """Return (meta_id, system_category_id_str, name) for the first German category.""" data = client.call( "accgetallfamily", {"a01call": "taskcategorysync", "a02call": "tasksync"}, ) cats = data["a01"]["r"]["r"]["updatedCreated"] # Also resolve list type for filtering. list_type: str | None = None try: list_data = client.call("taskgettasklists", {}) for lst in list_data.get("a00", {}).get("r", {}).get("r", []) or []: if lst.get("metaId") == list_id: list_type = lst.get("taskListType") break except FamilyWallError: pass for cat in cats: if cat.get("locale") != "de": continue if list_type and cat.get("taskListType") != list_type: continue meta_id: str = cat["metaId"] sys_id: str = str(cat.get("systemCategoryId", "")) name: str = cat.get("name", "") print(f" Using category: metaId={meta_id!r}, systemCategoryId={sys_id!r}, name={name!r}") return meta_id, sys_id, name raise RuntimeError("No German category found for list; adjust TEST_LIST_ID.") def _get_task_categories(task_obj: dict) -> list[str]: """Extract category names from a task API object.""" cats = task_obj.get("categories") or [] return [c.get("name", "") for c in cats if isinstance(c, dict)] def _delete_task(client: FamilyWallClient, task_id: str) -> None: try: client.call("metadelete", {"id": task_id}) except FamilyWallError as exc: print(f" [warn] Could not delete task {task_id}: {exc}") def main() -> None: email, password = get_credentials() with FamilyWallClient() as client: client.login(email, password) print(f"\nResolving category for list {TEST_LIST_ID!r} …") if FORCED_CATEGORY_META_ID: # Can't resolve name/numeric from meta_id easily; just use the meta_id. cat_meta = FORCED_CATEGORY_META_ID cat_numeric = cat_meta.split("_")[-1] if "_" in cat_meta else cat_meta cat_name = cat_meta else: cat_meta, cat_numeric, cat_name = _resolve_category(client, TEST_LIST_ID) value_map = { "meta_id": cat_meta, "numeric": cat_numeric, "name": cat_name, } print(f"\nTesting {len(CANDIDATES)} candidate parameter names …\n") results: list[tuple[str, str, bool]] = [] seen: set[tuple[str, str]] = set() # avoid duplicate tests for cand in CANDIDATES: key: str = cand["key"] # type: ignore[assignment] fmt: str = cand["fmt"] # type: ignore[assignment] value: str = value_map[fmt] pair = (key, value) if pair in seen: continue seen.add(pair) params: dict = { "taskListId": TEST_LIST_ID, "text": f"[TEST] cat-param-probe {key}={fmt}", key: value, } print(f" Testing {key!r} = {value!r} …", end=" ", flush=True) try: data = client.call("taskcreate2", params) except FamilyWallError as exc: print(f"API ERROR: {exc}") results.append((key, value, False)) continue task_obj = data.get("a00", {}).get("r", {}).get("r", {}) task_id: str = task_obj.get("metaId", "") applied_cats = _get_task_categories(task_obj) # A non-default category was applied when the name matches our target. success = any(cat_name in c or c == cat_name for c in applied_cats) if success: print(f"SUCCESS -> categories={applied_cats}") else: print(f"no effect -> categories={applied_cats}") results.append((key, value, success)) if task_id: _delete_task(client, task_id) client.logout() # ----------------------------------------------------------------------- # Summary # ----------------------------------------------------------------------- print("\n" + "=" * 60) print("RESULTS") print("=" * 60) hits = [(k, v) for k, v, ok in results if ok] misses = [(k, v) for k, v, ok in results if not ok] if hits: print(f"\nWORKING parameter(s) found ({len(hits)}):") for k, v in hits: print(f" {k!r} = {v!r}") else: print("\nNo working parameter found.") print(f"\nDid NOT work ({len(misses)}):") for k, v in misses: print(f" {k!r} = {v!r}") print() if __name__ == "__main__": main()