chatelet/__init__.py
Padreug cf9f8699da 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
2026-07-19 18:09:35 +02:00

106 lines
3.5 KiB
Python

import asyncio
from fastapi import APIRouter
from .crud import db
from .tasks import expire_holds_loop, wait_for_paid_invoices
from .views import chatelet_generic_router
from .views_api import chatelet_api_router
chatelet_static_files = [
{
"path": "/chatelet/static",
"name": "chatelet_static",
}
]
chatelet_ext: APIRouter = APIRouter(prefix="/chatelet", tags=["chatelet"])
chatelet_ext.include_router(chatelet_generic_router)
chatelet_ext.include_router(chatelet_api_router)
scheduled_tasks: list[asyncio.Task] = []
def chatelet_stop():
for task in scheduled_tasks:
task.cancel()
def chatelet_start():
from lnbits.tasks import create_permanent_unique_task
scheduled_tasks.append(
create_permanent_unique_task("chatelet_invoices", wait_for_paid_invoices)
)
scheduled_tasks.append(
create_permanent_unique_task("chatelet_hold_expiry", expire_holds_loop)
)
# Inbound Nostr: answer kind:22000 availability queries over relays via
# nostrclient (client-agnostic availability path). Self-backs-off if
# nostrclient isn't installed.
from .nostr.service import subscribe_inbound
scheduled_tasks.append(
create_permanent_unique_task("chatelet_availability_sub", subscribe_inbound)
)
# Expose the booking flow over the core LNbits nostr transport (kind-21000
# RPC) so an HTTP-allergic client can drive Chatelet over relays. Also wire
# the booking-owner resolver so the operator can stream booking
# settlements via subscribe_payments({tag:"chatelet", link_id:<booking_id>}).
# No-op if the core transport module isn't present in this LNbits build.
try:
from lnbits.core.services.nostr_transport.dispatcher import (
AUTH_ACCOUNT,
AUTH_NONE,
AUTH_WALLET,
register_rpc,
)
from lnbits.core.services.nostr_transport.subscriptions import (
register_link_owner_resolver,
)
except ImportError:
return
from .transport_rpcs import (
handle_availability,
handle_block_create,
handle_booking_get,
handle_booking_request,
handle_room_create,
handle_room_get,
handle_room_list,
handle_room_list_mine,
handle_room_publish,
handle_room_update,
resolve_chatelet_owner,
)
# operator (wallet-scoped)
register_rpc("chatelet_room_create", handle_room_create, AUTH_WALLET)
register_rpc("chatelet_room_update", handle_room_update, AUTH_WALLET)
register_rpc("chatelet_room_publish", handle_room_publish, AUTH_WALLET)
register_rpc("chatelet_block_create", handle_block_create, AUTH_WALLET)
register_rpc("chatelet_room_list_mine", handle_room_list_mine, AUTH_ACCOUNT)
# public (discovery + guest booking)
register_rpc("chatelet_room_list", handle_room_list, AUTH_NONE)
register_rpc("chatelet_room_get", handle_room_get, AUTH_NONE)
register_rpc("chatelet_availability", handle_availability, AUTH_NONE)
register_rpc("chatelet_booking_request", handle_booking_request, AUTH_NONE)
register_rpc("chatelet_booking_get", handle_booking_get, AUTH_NONE)
# tasks.py stamps extra["booking_id"] on settlement (see on_invoice_paid),
# so override the default link_extra_key ("link") to match.
register_link_owner_resolver(
"chatelet", resolve_chatelet_owner, link_extra_key="booking_id"
)
__all__ = [
"db",
"chatelet_ext",
"chatelet_start",
"chatelet_static_files",
"chatelet_stop",
]