5f5abfcbc7
- Consolidate wall post and activity tools in dedicated section - Remove duplicate entries from Lists & Tasks - Update README version header to v1.4.3 - Bump pyproject.toml to v1.4.4 - Fix unused import in find_category_param.py Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
195 lines
6.8 KiB
Python
195 lines
6.8 KiB
Python
"""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()
|