feat(tasks): support reminder write via dot-notation (v1.2.0)
- Add reminder_unit, reminder_value to create_task - Add reminder_unit, reminder_value, clear_reminder to update_task - Wire format verified: reminder.reminderUnit / .reminderValue / .reminderType (SNOOZE=active, NONE=clear) / .localId (optional) - Valid units: MINUTE, HOUR, DAY (WEEK rejected by enum decoder) - Clear requires full inactive block; partial updates reject with "task reminder invalid" - Flat top-level keys, JSON string and bracket notation are silently ignored by the server — confirmed via isolated fuzz per variant - Update SPEC.md, CLAUDE.md, README.md, CHANGELOG.md - Remove outdated "not supported" warning in update_task docstring
This commit is contained in:
@@ -10,6 +10,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
This project follows Semantic Versioning (SemVer).
|
This project follows Semantic Versioning (SemVer).
|
||||||
Breaking changes (removed tools, changed parameters) increment the major version.
|
Breaking changes (removed tools, changed parameters) increment the major version.
|
||||||
|
|
||||||
|
## [1.2.0] – 2026-04-17
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **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")
|
||||||
|
- `clear_reminder` (update_task only): remove reminder
|
||||||
|
- The correct wire format uses **dot-notation** subfields
|
||||||
|
(`reminder.reminderUnit`, `reminder.reminderValue`, `reminder.reminderType`,
|
||||||
|
`reminder.localId`) — flat top-level keys, JSON-string, and bracket-notation
|
||||||
|
are silently ignored by the server.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- `update_task` docstring: removed the "reminders not supported" warning — the
|
||||||
|
v1.1.2 write format was flat, which the FiZ encoder silently drops; the
|
||||||
|
dot-notation variant works on both Free and Premium accounts.
|
||||||
|
|
||||||
|
### Investigation notes (2026-04-17)
|
||||||
|
- Verified via isolated fuzz test (fresh test task per variant): only dot-notation
|
||||||
|
succeeds; every other encoding is accepted with HTTP 200 and `lastAction: UPDATED`
|
||||||
|
but the reminder remains at default `{reminderType: NONE, reminderValue: 0}`.
|
||||||
|
- `reminder.localId` is optional.
|
||||||
|
- Partial block updates (e.g. only `reminder.reminderType=NONE`) return
|
||||||
|
`task reminder invalid` — the full inactive block must be sent to clear.
|
||||||
|
- Valid `reminderUnit` values: `MINUTE`, `HOUR`, `DAY`. `WEEK` is rejected by the
|
||||||
|
API enum decoder.
|
||||||
|
- Reminder + recurrency can be set in a single `taskupdate2` call without interference.
|
||||||
|
|
||||||
## [1.1.2] – 2026-04-17
|
## [1.1.2] – 2026-04-17
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ und wird in Claude Desktop eingebunden.
|
|||||||
|
|
||||||
## Aktueller Stand
|
## Aktueller Stand
|
||||||
|
|
||||||
### Version: **v1.1.2** ← aktuell
|
### Version: **v1.2.0** ← aktuell
|
||||||
|
|
||||||
### Implementierte Tools
|
### Implementierte Tools
|
||||||
|
|
||||||
@@ -50,6 +50,8 @@ und wird in Claude Desktop eingebunden.
|
|||||||
- v0.9: Task-Wiederholungen + Erinnerungen (read-only)
|
- v0.9: Task-Wiederholungen + Erinnerungen (read-only)
|
||||||
- v0.10–v0.11: Essensplaner (read + write)
|
- v0.10–v0.11: Essensplaner (read + write)
|
||||||
- v1.0: Cleanup, Unified errors, Datumsvalidierung, Partial-Failure-Reporting (Details: CHANGELOG.md)
|
- v1.0: Cleanup, Unified errors, Datumsvalidierung, Partial-Failure-Reporting (Details: CHANGELOG.md)
|
||||||
|
- v1.1: Task-Recurrency-Write (flat top-level params)
|
||||||
|
- v1.2: Task-Reminder-Write via Dot-Notation (`reminder.*`) — verifiziert 2026-04-17
|
||||||
|
|
||||||
|
|
||||||
## Architektur-Entscheidungen
|
## Architektur-Entscheidungen
|
||||||
@@ -133,8 +135,8 @@ Fehler bei falschen Parametern kommen nicht immer auf Top-Level:
|
|||||||
| `taskupdate2` | `metaId`, `text`, `description`, `taskCategoryId`, `dueDate`, `assignee`, `taskListId` | – |
|
| `taskupdate2` | `metaId`, `text`, `description`, `taskCategoryId`, `dueDate`, `assignee`, `taskListId` | – |
|
||||||
| `taskupdate2` | `dueDate` löschen | `$empty` |
|
| `taskupdate2` | `dueDate` löschen | `$empty` |
|
||||||
| `taskupdate2` | `recurrencyDescriptor` (flach!) | `recurrency, recurrencyInterval, rrule, byDay, byMonthDay, recurrencyEndDate, endOccurence` als Top-Level-Parameter; löschen: `recurrency="NONE"` |
|
| `taskupdate2` | `recurrencyDescriptor` (flach!) | `recurrency, recurrencyInterval, rrule, byDay, byMonthDay, recurrencyEndDate, endOccurence` als Top-Level-Parameter; löschen: `recurrency="NONE"` |
|
||||||
| `taskupdate2` | **⚠️ Reminder read-only** | `reminderUnit`, `reminderValue`, `reminderType`, `localId` werden von der API ignoriert (verifiziert via FW_DEBUG auf Premium-Account). Service Worker der mobilen App transformiert Reminder-Requests — nicht reproduzierbar via direkter API. |
|
| `taskupdate2` | Reminder (Dot-Notation!) | `reminder.reminderUnit` (`MINUTE`/`HOUR`/`DAY`), `reminder.reminderValue` (String-Integer), `reminder.reminderType` (`SNOOZE`=aktiv, `NONE`=entfernen), `reminder.localId` optional. Entfernen: vollständigen Block mit `reminderType=NONE, reminderValue="0", reminderUnit=MINUTE` senden. Partielle Updates → `task reminder invalid`. |
|
||||||
| `taskupdate2` | **⚠️ Encoding** | FiZ `Ai()`-Encoder sendet Recurrency-Felder flach (verifiziert). Reminder: read-only. |
|
| `taskupdate2` | **⚠️ Encoding** | Recurrency flach top-level. Reminder **nur Dot-Notation** `reminder.*` — flache Keys, JSON-String, Brackets werden silent-ignored. |
|
||||||
| `taskmark` | `taskId`, `complete` | `"true"`/`"false"` |
|
| `taskmark` | `taskId`, `complete` | `"true"`/`"false"` |
|
||||||
| `metadelete` | `id` | metaId des Tasks / Rezepts |
|
| `metadelete` | `id` | metaId des Tasks / Rezepts |
|
||||||
| `wallmood` | `wall_message_id`, `moodType` | `"STAR"` für Like |
|
| `wallmood` | `wall_message_id`, `moodType` | `"STAR"` für Like |
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ MCP server for [Family Wall](https://www.familywall.com) — manage your family'
|
|||||||
| `delete_list` 🔒 | Permanently delete a list and all its tasks (system lists protected) |
|
| `delete_list` 🔒 | Permanently delete a list and all its tasks (system lists protected) |
|
||||||
| `create_category` 🔒 | Create a custom category (with optional icon) |
|
| `create_category` 🔒 | Create a custom category (with optional icon) |
|
||||||
| `delete_category` 🔒 | Delete a custom category (system categories protected) |
|
| `delete_category` 🔒 | Delete a custom category (system categories protected) |
|
||||||
| `create_task` 🔒 | Create a task (supports category, due date, assignees; use `"Äpfel (5x)"` for quantities) |
|
| `create_task` 🔒 | Create a task (category, due date, assignees, reminder; use `"Äpfel (5x)"` for quantities) |
|
||||||
| `update_task` 🔒 | Update text, category, due date, assignees, or move to a different list |
|
| `update_task` 🔒 | Update text, category, due date, assignees, recurrency, reminder, or move to a different list |
|
||||||
| `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 |
|
||||||
| `clear_list` 🔒 | Bulk-delete all tasks in a list (optional `only_open=True` keeps completed tasks) |
|
| `clear_list` 🔒 | Bulk-delete all tasks in a list (optional `only_open=True` keeps completed tasks) |
|
||||||
|
|||||||
@@ -213,15 +213,28 @@ POST https://api.familywall.com/api/taskupdate2
|
|||||||
**⚠️ Encoding:** Der FiZ-`Ai()`-Encoder serialisiert alle Felder **flach** als Top-Level-Form-Parameter.
|
**⚠️ Encoding:** Der FiZ-`Ai()`-Encoder serialisiert alle Felder **flach** als Top-Level-Form-Parameter.
|
||||||
Recurrency-Felder werden direkt auf Top-Level gesendet (verifiziert via xb-Encoder im JS-Bundle).
|
Recurrency-Felder werden direkt auf Top-Level gesendet (verifiziert via xb-Encoder im JS-Bundle).
|
||||||
|
|
||||||
**⚠️ Reminder-Schreibzugriff nicht möglich:**
|
**Reminder-Schreibzugriff (verifiziert 2026-04-17):**
|
||||||
Alle Versuche, `reminderUnit`, `reminderValue`, `reminderType`, `localId` via `taskupdate2`
|
|
||||||
zu setzen, wurden ignoriert — die API antwortet mit HTTP 200 und `lastAction: UPDATED`,
|
Reminder werden via Dot-Notation als Unterfelder gesendet:
|
||||||
aber `reminder` bleibt unverändert auf dem Default-Wert `{reminderUnit: MINUTE, reminderType: NONE, reminderValue: 0}`.
|
|
||||||
Auch alternative Encodings (JSON-String, PHP-Bracket, verschachtelt) und alternative Endpoints
|
| Parameter | Wert |
|
||||||
(`tasksetalert`, `taskalertput` etc.) wurden erprobt — alle Endpoints sind nicht registriert.
|
|---|---|
|
||||||
**Ursache:** Reminder-Updates werden vom Service Worker der mobilen App abgefangen und transformiert.
|
| `reminder.reminderUnit` | `"MINUTE"` \| `"HOUR"` \| `"DAY"` (WEEK wird abgelehnt) |
|
||||||
Diese Transformation ist nicht reproduzierbar ohne den Service Worker.
|
| `reminder.reminderValue` | nicht-negativer Integer als String (z.B. `"30"`) |
|
||||||
Reminder sind daher **read-only** (wie in v0.9 dokumentiert).
|
| `reminder.reminderType` | `"SNOOZE"` (aktiv) oder `"NONE"` (Reminder entfernen) |
|
||||||
|
| `reminder.localId` | optional; `"0"` akzeptiert |
|
||||||
|
|
||||||
|
**Wichtig:**
|
||||||
|
- Nur Dot-Notation funktioniert. Flache Top-Level-Keys (`reminderUnit`, `reminderValue`, …),
|
||||||
|
JSON-String (`reminder={…}`) und Bracket-Notation (`reminder[reminderUnit]`) werden
|
||||||
|
**silent-ignored** (HTTP 200, Reminder unverändert).
|
||||||
|
- Partielle Reminder-Updates (z.B. nur `reminder.reminderType=NONE`) liefern
|
||||||
|
`task reminder invalid` — immer vollständigen Block senden.
|
||||||
|
- `reminder=$empty` wird silent-ignored; zum Entfernen muss `reminder.reminderType=NONE`
|
||||||
|
mit `reminder.reminderValue=0` und `reminder.reminderUnit=MINUTE` gesendet werden.
|
||||||
|
- Reminder-Felder lassen sich in einem einzigen `taskupdate2`-Call gemeinsam mit
|
||||||
|
`recurrency`/`dueDate`/`text` setzen — keine Interferenz.
|
||||||
|
- `$empty` auf Unterfelder (`reminder.reminderValue=$empty`) führt zu Parse-Fehlern.
|
||||||
|
|
||||||
**Response:**
|
**Response:**
|
||||||
```
|
```
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "mcp-familywall"
|
name = "mcp-familywall"
|
||||||
version = "1.1.2"
|
version = "1.2.0"
|
||||||
description = "MCP server for Family Wall — manage your family's circles, lists, tasks, recipes, and meal plan via Claude"
|
description = "MCP server for Family Wall — manage your family's circles, lists, tasks, recipes, and meal plan via Claude"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|||||||
@@ -807,6 +807,59 @@ def get_activities(limit: int = 20) -> str:
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# Reminder units accepted by the Family Wall API (verified 2026-04-17 via
|
||||||
|
# taskupdate2 fuzz: WEEK is rejected, MINUTE/HOUR/DAY succeed).
|
||||||
|
_VALID_REMINDER_UNITS = ("MINUTE", "HOUR", "DAY")
|
||||||
|
|
||||||
|
|
||||||
|
def _build_reminder_params(
|
||||||
|
reminder_unit: str | None,
|
||||||
|
reminder_value: int | None,
|
||||||
|
clear_reminder: bool,
|
||||||
|
) -> tuple[dict[str, Any], str | None]:
|
||||||
|
"""Return (params, error_message). Params are the dot-notation reminder.* fields.
|
||||||
|
|
||||||
|
Encoding note: taskupdate2 only accepts reminders via dot-notation
|
||||||
|
(``reminder.reminderUnit``, ``reminder.reminderValue``, ``reminder.reminderType``).
|
||||||
|
Flat top-level keys, JSON strings, and bracket notation are silently ignored
|
||||||
|
by the server — verified 2026-04-17.
|
||||||
|
|
||||||
|
Clearing requires sending the full "inactive" block
|
||||||
|
(reminderType=NONE, reminderValue=0, reminderUnit=MINUTE, localId=0) —
|
||||||
|
partial updates return ``"task reminder invalid"``.
|
||||||
|
"""
|
||||||
|
if clear_reminder and (reminder_unit is not None or reminder_value is not None):
|
||||||
|
return {}, "'clear_reminder' cannot be combined with 'reminder_unit' or 'reminder_value'."
|
||||||
|
|
||||||
|
if clear_reminder:
|
||||||
|
return {
|
||||||
|
"reminder.reminderUnit": "MINUTE",
|
||||||
|
"reminder.reminderValue": "0",
|
||||||
|
"reminder.reminderType": "NONE",
|
||||||
|
"reminder.localId": "0",
|
||||||
|
}, None
|
||||||
|
|
||||||
|
if reminder_unit is None and reminder_value is None:
|
||||||
|
return {}, None
|
||||||
|
|
||||||
|
if reminder_unit is None or reminder_value is None:
|
||||||
|
return {}, "'reminder_unit' and 'reminder_value' must be provided together."
|
||||||
|
|
||||||
|
if reminder_unit not in _VALID_REMINDER_UNITS:
|
||||||
|
allowed = ", ".join(_VALID_REMINDER_UNITS)
|
||||||
|
return {}, f"Invalid reminder_unit {reminder_unit!r}. Allowed: {allowed}."
|
||||||
|
|
||||||
|
if reminder_value < 0:
|
||||||
|
return {}, "'reminder_value' must be non-negative."
|
||||||
|
|
||||||
|
return {
|
||||||
|
"reminder.reminderUnit": reminder_unit,
|
||||||
|
"reminder.reminderValue": str(reminder_value),
|
||||||
|
"reminder.reminderType": "SNOOZE",
|
||||||
|
"reminder.localId": "0",
|
||||||
|
}, None
|
||||||
|
|
||||||
|
|
||||||
def _authenticated_call(endpoint: str, params: dict[str, Any]) -> dict[str, Any]:
|
def _authenticated_call(endpoint: str, params: dict[str, Any]) -> dict[str, Any]:
|
||||||
"""Login, call *endpoint* with *params*, logout, and return the response body.
|
"""Login, call *endpoint* with *params*, logout, and return the response body.
|
||||||
|
|
||||||
@@ -847,6 +900,8 @@ def create_task(
|
|||||||
category_id: str | None = None,
|
category_id: str | None = None,
|
||||||
due_date: str | None = None,
|
due_date: str | None = None,
|
||||||
assignee_ids: list[str] | None = None,
|
assignee_ids: list[str] | None = None,
|
||||||
|
reminder_unit: str | None = None,
|
||||||
|
reminder_value: int | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Create a new task in the given list.
|
"""Create a new task in the given list.
|
||||||
|
|
||||||
@@ -878,10 +933,20 @@ def create_task(
|
|||||||
due_date: Optional due date in ISO 8601 format (e.g. ``"2026-04-30T18:00:00"``).
|
due_date: Optional due date in ISO 8601 format (e.g. ``"2026-04-30T18:00:00"``).
|
||||||
assignee_ids: Optional list of member IDs from get_members to assign the task
|
assignee_ids: Optional list of member IDs from get_members to assign the task
|
||||||
(e.g. ``["23431898"]``). Empty list assigns to nobody.
|
(e.g. ``["23431898"]``). Empty list assigns to nobody.
|
||||||
|
reminder_unit: Reminder time unit — ``"MINUTE"``, ``"HOUR"``, or ``"DAY"``.
|
||||||
|
Must be provided together with *reminder_value*. Requires Family Wall Premium.
|
||||||
|
reminder_value: Reminder offset before the due date (e.g. ``30`` for
|
||||||
|
"30 minutes before"). Must be provided together with *reminder_unit*.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
JSON with the new task's metaId on success, or an error message.
|
JSON with the new task's metaId on success, or an error message.
|
||||||
"""
|
"""
|
||||||
|
reminder_params, reminder_error = _build_reminder_params(
|
||||||
|
reminder_unit, reminder_value, clear_reminder=False
|
||||||
|
)
|
||||||
|
if reminder_error:
|
||||||
|
return _err(reminder_error)
|
||||||
|
|
||||||
params: dict[str, Any] = {"taskListId": list_id, "text": text}
|
params: dict[str, Any] = {"taskListId": list_id, "text": text}
|
||||||
if description:
|
if description:
|
||||||
params["description"] = description
|
params["description"] = description
|
||||||
@@ -891,6 +956,7 @@ def create_task(
|
|||||||
params["dueDate"] = due_date
|
params["dueDate"] = due_date
|
||||||
if assignee_ids is not None:
|
if assignee_ids is not None:
|
||||||
params["assignee"] = assignee_ids if assignee_ids else ""
|
params["assignee"] = assignee_ids if assignee_ids else ""
|
||||||
|
params.update(reminder_params)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = _authenticated_call("taskcreate2", params)
|
data = _authenticated_call("taskcreate2", params)
|
||||||
@@ -930,17 +996,15 @@ def update_task(
|
|||||||
recurrency_interval: int | None = None,
|
recurrency_interval: int | None = None,
|
||||||
rrule: str | None = None,
|
rrule: str | None = None,
|
||||||
clear_recurrency: bool = False,
|
clear_recurrency: bool = False,
|
||||||
|
reminder_unit: str | None = None,
|
||||||
|
reminder_value: int | None = None,
|
||||||
|
clear_reminder: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Update an existing task's fields.
|
"""Update an existing task's fields.
|
||||||
|
|
||||||
IMPORTANT: Ask the user for confirmation before calling this tool.
|
IMPORTANT: Ask the user for confirmation before calling this tool.
|
||||||
At least one parameter besides *task_id* must be provided.
|
At least one parameter besides *task_id* must be provided.
|
||||||
|
|
||||||
NOTE: Setting or clearing reminders is **not supported** via this tool.
|
|
||||||
The Family Wall API does not accept reminder writes through ``taskupdate2``
|
|
||||||
— reminder updates are handled exclusively by the mobile app's Service
|
|
||||||
Worker and cannot be replicated via direct API calls.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
task_id: Task metaId from get_tasks.
|
task_id: Task metaId from get_tasks.
|
||||||
text: New title text (omit to leave unchanged).
|
text: New title text (omit to leave unchanged).
|
||||||
@@ -969,6 +1033,13 @@ def update_task(
|
|||||||
Overrides simple recurrency fields when set.
|
Overrides simple recurrency fields when set.
|
||||||
clear_recurrency: Set to ``True`` to remove the recurrence rule from the task.
|
clear_recurrency: Set to ``True`` to remove the recurrence rule from the task.
|
||||||
Cannot be used together with *recurrency*.
|
Cannot be used together with *recurrency*.
|
||||||
|
reminder_unit: Reminder time unit — ``"MINUTE"``, ``"HOUR"``, or ``"DAY"``.
|
||||||
|
Must be provided together with *reminder_value*. Requires Family Wall
|
||||||
|
Premium. Cannot be used together with *clear_reminder*.
|
||||||
|
reminder_value: Reminder offset before the due date (e.g. ``30`` for
|
||||||
|
"30 minutes before"). Must be provided together with *reminder_unit*.
|
||||||
|
clear_reminder: Set to ``True`` to remove the reminder from the task.
|
||||||
|
Cannot be used together with *reminder_unit* or *reminder_value*.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
JSON success indicator or an error message.
|
JSON success indicator or an error message.
|
||||||
@@ -978,6 +1049,12 @@ def update_task(
|
|||||||
if clear_recurrency and recurrency is not None:
|
if clear_recurrency and recurrency is not None:
|
||||||
return _err("'clear_recurrency' and 'recurrency' cannot be used together.")
|
return _err("'clear_recurrency' and 'recurrency' cannot be used together.")
|
||||||
|
|
||||||
|
reminder_params, reminder_error = _build_reminder_params(
|
||||||
|
reminder_unit, reminder_value, clear_reminder
|
||||||
|
)
|
||||||
|
if reminder_error:
|
||||||
|
return _err(reminder_error)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
text is None
|
text is None
|
||||||
and description is None
|
and description is None
|
||||||
@@ -988,11 +1065,13 @@ def update_task(
|
|||||||
and list_id is None
|
and list_id is None
|
||||||
and recurrency is None
|
and recurrency is None
|
||||||
and not clear_recurrency
|
and not clear_recurrency
|
||||||
|
and not reminder_params
|
||||||
):
|
):
|
||||||
return _err(
|
return _err(
|
||||||
"At least one of 'text', 'description', 'category_id', 'due_date',"
|
"At least one of 'text', 'description', 'category_id', 'due_date',"
|
||||||
" 'clear_due_date', 'assignee_ids', 'list_id', 'recurrency', or"
|
" 'clear_due_date', 'assignee_ids', 'list_id', 'recurrency',"
|
||||||
" 'clear_recurrency' must be provided."
|
" 'clear_recurrency', 'reminder_unit'/'reminder_value', or"
|
||||||
|
" 'clear_reminder' must be provided."
|
||||||
)
|
)
|
||||||
|
|
||||||
params: dict[str, Any] = {"metaId": task_id}
|
params: dict[str, Any] = {"metaId": task_id}
|
||||||
@@ -1022,6 +1101,9 @@ def update_task(
|
|||||||
if rrule is not None:
|
if rrule is not None:
|
||||||
params["rrule"] = rrule
|
params["rrule"] = rrule
|
||||||
|
|
||||||
|
# Reminder fields use dot-notation `reminder.*` (verified: flat keys ignored).
|
||||||
|
params.update(reminder_params)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_authenticated_call("taskupdate2", params)
|
_authenticated_call("taskupdate2", params)
|
||||||
except RuntimeError as exc:
|
except RuntimeError as exc:
|
||||||
|
|||||||
Reference in New Issue
Block a user