feat: relay publish + availability subscription via nostrclient (#2)

Wire the Nostr layer through the nostrclient extension's relay manager
in-process (spirekeeper pattern), replacing the sketch stubs:

- _sign_and_publish: sign as operator (resolve_signer), optional NIP-44
  encrypt, publish via nostr_client.relay_manager.publish_message. Soft-
  fails (logs, returns None) if no operator onboarded, nostrclient absent,
  or signer can't encrypt — never crashes the booking flow.
- publish_listing (30402) + publish_block_calendar (31923): public, work
  today (sign_event only).
- publish_reservation (30078): NIP-44 encrypted to guest; soft-fails on a
  LocalSigner until a bunker/server-signing signer lands (lnbits#18).
- subscribe_inbound: permanent task answering kind:22000 availability
  queries with kind:22001 (plaintext — availability is public info), the
  client-agnostic availability path parallel to the RPC.

events.py: drop the _plaintext scaffolding (service.py encrypts content in
place) and a broken {operator_pubkey} calendar tag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VUQCfdqiLSsFS2jcGnaFD
This commit is contained in:
Padreug 2026-07-19 18:09:35 +02:00
commit cf9f8699da
3 changed files with 182 additions and 63 deletions

View file

@ -67,13 +67,13 @@ def build_reservation_event(booking: Booking, guest_pubkey: str) -> dict:
}
return {
"kind": KIND_RESERVATION,
# placeholder — service.py replaces with NIP-44(payload) for guest
# plaintext JSON; service.py NIP-44-encrypts this to the guest before
# signing (publish_reservation passes encrypt_to=guest_pubkey).
"content": json.dumps(payload),
"tags": [
["d", booking.id],
["p", guest_pubkey],
],
"_plaintext": payload, # consumed + stripped by service.py before signing
}
@ -91,7 +91,6 @@ def build_block_calendar_event(
["title", f"{room.title} — unavailable"],
["start", start_date],
["end", end_date],
["a", f"{KIND_LISTING}:{{operator_pubkey}}:{room.id}"], # link to listing
],
}
@ -99,8 +98,13 @@ def build_block_calendar_event(
def build_availability_response(
result: AvailabilityResult, requester_pubkey: str
) -> dict:
"""Aiolabs kind:22001 (ephemeral) reply to an availability query. Content
MUST be NIP-44 encrypted to the requester by service.py."""
"""Aiolabs kind:22001 (ephemeral) reply to an availability query.
Plaintext by design: room availability for a date range is public
information (the same facts published in the NIP-52 calendar), so this
needs no encryption which also means it works today without a bunker/
server-signing signer (only sign_event is required, not nip44_encrypt).
p-tagged to the requester so their client can match the reply."""
payload = {
"room_id": result.room_id,
"check_in": result.check_in,
@ -115,5 +119,4 @@ def build_availability_response(
"kind": KIND_AVAILABILITY_RESPONSE,
"content": json.dumps(payload),
"tags": [["p", requester_pubkey]],
"_plaintext": payload,
}