- nostr/ vendors NostrEvent + the nostrclient WebSocket bridge from the events extension, retagged [TASKS] / subscription-id "tasks-*". - nostr_publisher builds kind 31922 with the `event-type: task` tag (per aiolabs/webapp#25 — disambiguates from kind-31922 activities on shared relays), kind 31925 with task-status / occurrence / completed_at, and kind 5 deletions for both. - nostr_hooks bridges task/completion mutations to the publisher and persists the resulting nostr_event_id back onto the local row. - nostr_sync subscribes to {31922, 31925, 5/#k} and filters 31922 client-side on `event-type: task` because most relays don't index custom single-letter tags. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
97 lines
3.2 KiB
Python
97 lines
3.2 KiB
Python
"""Helpers that bridge task-mutation handlers to the Nostr publisher.
|
|
|
|
Sits between views_api and nostr_publisher so we don't pull the publisher
|
|
through the views module (which would create an import cycle via models)."""
|
|
|
|
from loguru import logger
|
|
|
|
from .crud import update_task
|
|
from .models import Task, TaskCompletion
|
|
from .nostr_publisher import (
|
|
publish_completion_delete_to_nostr,
|
|
publish_completion_to_nostr,
|
|
publish_task_to_nostr,
|
|
)
|
|
|
|
|
|
async def _account_keys(wallet_id: str) -> tuple[str, str] | None:
|
|
"""Fetch (pubkey, prvkey) for the wallet's owning account. Returns None
|
|
when the account is missing keys, so callers can skip cleanly."""
|
|
from lnbits.core.crud.users import get_account
|
|
from lnbits.core.crud.wallets import get_wallet
|
|
|
|
wallet_obj = await get_wallet(wallet_id)
|
|
if not wallet_obj:
|
|
return None
|
|
account = await get_account(wallet_obj.user)
|
|
if not account or not account.pubkey or not account.prvkey: # type: ignore[attr-defined]
|
|
return None
|
|
return account.pubkey, account.prvkey # type: ignore[attr-defined]
|
|
|
|
|
|
async def publish_or_delete_task_event(
|
|
task: Task, *, delete: bool = False
|
|
) -> None:
|
|
"""Publish (or delete-publish) the NIP-52 kind 31922 for `task`.
|
|
|
|
Errors are logged and swallowed so a Nostr outage doesn't break the
|
|
HTTP flow that triggered the publish."""
|
|
try:
|
|
from . import nostr_client
|
|
|
|
keys = await _account_keys(task.wallet)
|
|
if not keys:
|
|
return
|
|
pubkey, prvkey = keys
|
|
|
|
nostr_event = await publish_task_to_nostr(
|
|
nostr_client, task, pubkey, prvkey, delete=delete
|
|
)
|
|
if nostr_event and not delete:
|
|
task.nostr_event_id = nostr_event.id
|
|
task.nostr_event_created_at = nostr_event.created_at
|
|
await update_task(task)
|
|
except Exception as exc:
|
|
logger.warning(f"[TASKS] Nostr task publish failed: {exc}")
|
|
|
|
|
|
async def publish_task_completion(
|
|
task: Task, completion: TaskCompletion
|
|
) -> str | None:
|
|
"""Publish a kind 31925 completion. Returns the Nostr event id so the
|
|
caller can persist it as the completion's primary key, replacing the
|
|
locally-generated hash from the optimistic insert."""
|
|
try:
|
|
from . import nostr_client
|
|
|
|
keys = await _account_keys(task.wallet)
|
|
if not keys:
|
|
return None
|
|
pubkey, prvkey = keys
|
|
|
|
nostr_event = await publish_completion_to_nostr(
|
|
nostr_client, task.address, completion, pubkey, prvkey
|
|
)
|
|
return nostr_event.id if nostr_event else None
|
|
except Exception as exc:
|
|
logger.warning(f"[TASKS] Nostr completion publish failed: {exc}")
|
|
return None
|
|
|
|
|
|
async def publish_completion_delete(
|
|
wallet_id: str, completion_id: str
|
|
) -> None:
|
|
"""Publish a NIP-09 delete for a previously-published completion."""
|
|
try:
|
|
from . import nostr_client
|
|
|
|
keys = await _account_keys(wallet_id)
|
|
if not keys:
|
|
return
|
|
pubkey, prvkey = keys
|
|
|
|
await publish_completion_delete_to_nostr(
|
|
nostr_client, completion_id, pubkey, prvkey
|
|
)
|
|
except Exception as exc:
|
|
logger.warning(f"[TASKS] Nostr completion delete failed: {exc}")
|