nostr_tools.Client¶
- class nostr_tools.Client[source]¶
Bases:
objectAsync WebSocket client for connecting to Nostr relays.
This class provides async methods for subscribing to events, publishing events, and managing WebSocket connections with proper error handling and timeout support. It supports both clearnet and Tor relays via SOCKS5 proxy. The client implements async context manager protocol for automatic connection management.
Examples
Basic usage with context manager:
>>> relay = Relay("wss://relay.damus.io") >>> client = Client(relay, timeout=15) >>> async with client: ... # Client is automatically connected ... filter = Filter(kinds=[1], limit=10) ... events = await fetch_events(client, filter)
Connect to Tor relay:
>>> tor_relay = Relay("wss://relay.onion") >>> client = Client( ... relay=tor_relay, ... socks5_proxy_url="socks5://127.0.0.1:9050" ... ) >>> async with client: ... # Use Tor connection ... pass
Manual connection management:
>>> client = Client(relay) >>> await client.connect() >>> try: ... # Perform operations ... sub_id = await client.subscribe(filter) ... finally: ... await client.disconnect()
Subscribe and publish:
>>> async with client: ... # Subscribe to events ... filter = Filter(kinds=[1], authors=["abc123..."]) ... sub_id = await client.subscribe(filter) ... ... # Publish an event ... success = await client.publish(event) ... ... # Listen for events ... async for message in client.listen_events(sub_id): ... event = Event.from_dict(message[2]) ... print(event.content) ... break ... ... await client.unsubscribe(sub_id)
- Raises:
ClientValidationError – If client configuration is invalid (relay type, timeout, proxy).
ClientConnectionError – If connection to relay fails.
Methods
__init__(relay[, timeout, socks5_proxy_url])authenticate(event)Authenticate with the relay using a NIP-42 event.
connect()Establish WebSocket connection to the relay.
Create appropriate connector based on network type.
Close WebSocket connection and cleanup all resources.
from_dict(data)Create Client from dictionary.
listen()Listen for all messages from the relay.
listen_events(subscription_id)Listen for events from a specific subscription.
publish(event)Publish an event to the relay.
send_message(message)Send a message to the relay.
session([connector])Create HTTP session with specified connector.
subscribe(filter[, subscription_id])Subscribe to events matching the given filter criteria.
to_dict()Convert Client to dictionary.
unsubscribe(subscription_id)Unsubscribe from events.
validate()Validate the Client instance configuration.
Attributes
Get list of currently active subscription IDs.
Check if client is currently connected to the relay.
Check if the Client configuration is valid.
"socks5://host:port"
Connection and operation timeout in seconds.
The Nostr relay to connect to.
- relay: Relay¶
The Nostr relay to connect to. Must be a valid Relay instance with properly formatted WebSocket URL.
- timeout: int | None = 10¶
Connection and operation timeout in seconds. Default is 10 seconds. Set to None for no timeout.
- socks5_proxy_url: str | None = None¶
“socks5://host:port”
- Type:
SOCKS5 proxy URL for Tor relays. Required when connecting to .onion relays. Format
- __post_init__()[source]¶
Validate Client configuration after initialization.
This method is automatically called after the dataclass is created. It validates all configuration parameters to ensure the client is properly configured before use.
- Raises:
ClientValidationError – If configuration is invalid (negative timeout, missing proxy, etc.).
- Return type:
None
- validate()[source]¶
Validate the Client instance configuration.
Performs comprehensive validation including: - Type checking for relay, timeout, and proxy URL - Timeout value validation (must be non-negative if set) - SOCKS5 proxy requirement check for Tor relays
- Raises:
ClientValidationError – If any attribute is invalid: - relay must be a Relay instance - timeout must be int or None and non-negative - socks5_proxy_url must be str or None - SOCKS5 proxy URL is required for Tor relays
- Return type:
None
Examples
>>> client = Client(relay, timeout=10) >>> client.validate() # Passes validation
>>> invalid_client = Client(relay, timeout=-5) >>> invalid_client.validate() # Raises ClientValidationError
- property is_valid: bool¶
Check if the Client configuration is valid.
- Returns:
True if valid, False otherwise
- Return type:
- to_dict()[source]¶
Convert Client to dictionary.
- Returns:
- Dictionary representation of Client with keys:
relay: Relay configuration dictionary
timeout: Connection timeout in seconds
socks5_proxy_url: SOCKS5 proxy URL for Tor relays
- Return type:
Examples
>>> client = Client(relay, timeout=10) >>> client_dict = client.to_dict() >>> print(client_dict['timeout']) 10
- async __aenter__()[source]¶
Async context manager entry.
Automatically connects to the relay when entering the context. This method is called when using the client in an async with statement.
- Returns:
Self for use in async with statement
- Return type:
Examples
>>> async with Client(relay) as client: ... # Client is automatically connected ... await client.publish(event)
- async __aexit__(exc_type, exc_val, exc_tb)[source]¶
Async context manager exit.
Automatically disconnects from the relay when exiting the context. This method is called when leaving the async with statement.
- Parameters:
exc_type (type) – Exception type if an exception occurred
exc_val (Exception) – Exception value if an exception occurred
exc_tb (TracebackType) – Exception traceback if an exception occurred
- Return type:
None
Examples
>>> async with Client(relay) as client: ... # Client is automatically connected ... pass >>> # Client is automatically disconnected here
- connector()[source]¶
Create appropriate connector based on network type.
- Returns:
TCPConnector for clearnet or ProxyConnector for Tor
- Return type:
Union[TCPConnector, ProxyConnector]
- Raises:
ClientConnectionError – If SOCKS5 proxy URL required for Tor but not provided
- session(connector=None)[source]¶
Create HTTP session with specified connector.
- Parameters:
connector (TCPConnector | ProxyConnector | None) – Optional connector to use (default: auto-detect)
- Returns:
HTTP session for making requests
- Return type:
ClientSession
- async connect()[source]¶
Establish WebSocket connection to the relay.
This method attempts to establish a WebSocket connection to the relay, trying both WSS (secure) and WS (insecure) protocols, preferring WSS. Creates HTTP session with appropriate connector (TCP or SOCKS5 proxy) based on relay network type.
The method is idempotent - calling it when already connected will simply return without error.
- Raises:
ClientConnectionError – If connection fails for any reason: - Network unreachable - Invalid relay URL - Timeout exceeded - SOCKS5 proxy connection failed (for Tor)
- Return type:
None
Examples
>>> client = Client(relay) >>> await client.connect() >>> print(client.is_connected) True
>>> # With timeout handling >>> try: ... await client.connect() ... except ClientConnectionError as e: ... print(f"Connection failed: {e}")
- async disconnect()[source]¶
Close WebSocket connection and cleanup all resources.
This method properly closes the WebSocket connection, HTTP session, and clears all active subscriptions. It’s safe to call even if not connected.
Resources cleaned up: - WebSocket connection closed gracefully - HTTP session terminated - All subscription state cleared - Internal buffers released
Examples
>>> await client.connect() >>> # ... perform operations ... >>> await client.disconnect() >>> print(client.is_connected) False
- Return type:
None
- async send_message(message)[source]¶
Send a message to the relay.
- Parameters:
message (
list[Any]) – Message to send as a list (will be JSON encoded)- Raises:
ClientConnectionError – If not connected or send fails
TypeError – If message is not a list
- Return type:
None
- async subscribe(filter, subscription_id=None)[source]¶
Subscribe to events matching the given filter criteria.
Sends a REQ message to the relay with the specified filter. The relay will send all stored events matching the filter, followed by real-time events as they arrive. A unique subscription ID is generated if not provided.
- Parameters:
filter (
Filter) – Event filter criteria defining which events to receive. Can filter by kinds, authors, tags, time range, etc.subscription_id (
Optional[str]) – Custom subscription ID. If None, a UUID4 will be automatically generated. Useful for tracking multiple subscriptions.
- Returns:
- Subscription ID used to identify events and manage the subscription.
Use this ID with listen_events() and unsubscribe().
- Return type:
- Raises:
ClientConnectionError – If not connected or subscription fails.
TypeError – If filter is not a Filter instance.
ClientSubscriptionError – If subscription_id already exists and is active.
Examples
Subscribe with auto-generated ID:
>>> filter = Filter(kinds=[1], limit=10) >>> sub_id = await client.subscribe(filter) >>> print(f"Subscribed with ID: {sub_id}")
Subscribe with custom ID:
>>> filter = Filter(authors=["abc123..."]) >>> sub_id = await client.subscribe(filter, "my-custom-sub") >>> async for msg in client.listen_events(sub_id): ... process_event(msg)
Multiple subscriptions:
>>> # Subscribe to different event types >>> notes_sub = await client.subscribe(Filter(kinds=[1])) >>> reactions_sub = await client.subscribe(Filter(kinds=[7]))
- async unsubscribe(subscription_id)[source]¶
Unsubscribe from events.
- Parameters:
subscription_id (
str) – Subscription ID to close- Raises:
TypeError – If subscription_id is not a string
ClientSubscriptionError – If subscription doesn’t exist or is not active
- Return type:
None
- async publish(event)[source]¶
Publish an event to the relay.
Sends an EVENT message to the relay with the provided event. Waits for an OK response from the relay to determine if the event was accepted. The event must be properly signed and validated before publishing. Returns on success or raises ClientPublicationError on failure.
- Parameters:
event (
Event) – Event to publish. Must be a valid, signed Event instance. The event will be validated by the relay.- Returns:
Returns nothing on success. Raises ClientPublicationError on failure.
- Return type:
None
- Raises:
ClientPublicationError – If the relay rejects the event or no OK response is received. Rejection can occur due to: - Invalid signature - Spam/rate limiting - Relay policy violations - Duplicate event - No response from relay
ClientConnectionError – If not connected or communication fails.
TypeError – If event is not an Event instance.
Examples
Publish a text note:
>>> from nostr_tools import generate_event >>> event_dict = generate_event( ... private_key, public_key, ... kind=1, tags=[], content="Hello Nostr!" ... ) >>> event = Event.from_dict(event_dict) >>> try: ... await client.publish(event) ... print("Event published successfully!") ... except ClientPublicationError as e: ... print(f"Event rejected: {e}")
Publish with error handling:
>>> try: ... await client.publish(event) ... print("Success!") ... except ClientPublicationError as e: ... print(f"Publish failed: {e}") ... except ClientConnectionError as e: ... print(f"Connection error: {e}")
- async authenticate(event)[source]¶
Authenticate with the relay using a NIP-42 event.
- Parameters:
event (
Event) – Authentication event (must be kind 22242)- Returns:
True if authentication successful, False otherwise
- Return type:
- Raises:
ValueError – If event kind is not 22242
TypeError – If event is not an Event instance
- async listen()[source]¶
Listen for all messages from the relay.
This async generator continuously listens for messages from the relay and yields them as they arrive. Messages are automatically parsed from JSON format. The generator continues until timeout, connection closure, or error.
Message types from relay: - [“EVENT”, subscription_id, event_dict]: New event matching subscription - [“EOSE”, subscription_id]: End of stored events for subscription - [“OK”, event_id, success, message]: Response to EVENT/AUTH message - [“NOTICE”, message]: Relay notification or error message - [“CLOSED”, subscription_id, message]: Subscription closed by relay
- Yields:
list[Any]–- Relay messages as parsed JSON lists. Format depends on
message type (see above).
- Raises:
ClientConnectionError – If not connected, connection fails, or encounters WebSocket errors.
- Return type:
AsyncGenerator[list[Any], None]
Examples
Listen to all relay messages:
>>> async for message in client.listen(): ... msg_type = message[0] ... if msg_type == "EVENT": ... sub_id, event_dict = message[1], message[2] ... event = Event.from_dict(event_dict) ... print(f"Received event: {event.content}") ... elif msg_type == "EOSE": ... print(f"End of stored events for {message[1]}") ... elif msg_type == "NOTICE": ... print(f"Relay notice: {message[1]}")
With timeout handling:
>>> try: ... async for message in client.listen(): ... process_message(message) ... except asyncio.TimeoutError: ... print("No messages received within timeout")
- async listen_events(subscription_id)[source]¶
Listen for events from a specific subscription.
This method filters messages to only yield events from the specified subscription until the subscription ends.
- Parameters:
subscription_id (
str) – Subscription to listen to- Yields:
list[Any]– Events received from the subscription- Raises:
TypeError – If subscription_id is not a string
- Return type:
AsyncGenerator[list[Any], None]
- property is_connected: bool¶
Check if client is currently connected to the relay.
This property checks if the WebSocket connection is active and not closed.
- Returns:
True if connected and WebSocket is open, False otherwise.
- Return type:
Examples
>>> if client.is_connected: ... await client.publish(event) ... else: ... await client.connect()
>>> async with client: ... assert client.is_connected # True inside context >>> assert not client.is_connected # False after context exit
- property active_subscriptions: list[str]¶
Get list of currently active subscription IDs.
Returns list of subscription IDs that are active (not closed/unsubscribed). Useful for tracking and managing multiple concurrent subscriptions.
- Returns:
- List of subscription IDs that are currently active.
Empty list if no active subscriptions.
- Return type:
Examples
>>> sub1 = await client.subscribe(Filter(kinds=[1])) >>> sub2 = await client.subscribe(Filter(kinds=[7])) >>> print(client.active_subscriptions) ['sub_id_1', 'sub_id_2']
>>> await client.unsubscribe(sub1) >>> print(client.active_subscriptions) ['sub_id_2']
>>> # Close all active subscriptions >>> for sub_id in client.active_subscriptions: ... await client.unsubscribe(sub_id)