parent
15079c3e58
commit
35584a230f
16 changed files with 2405 additions and 2752 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import json
|
||||
import time
|
||||
from typing import Any, Awaitable, Callable, List, Optional
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Any
|
||||
|
||||
from fastapi import WebSocket
|
||||
from lnbits.helpers import urlsafe_short_hash
|
||||
|
|
@ -25,17 +26,17 @@ class NostrClientConnection:
|
|||
def __init__(self, relay_id: str, websocket: WebSocket):
|
||||
self.websocket = websocket
|
||||
self.relay_id = relay_id
|
||||
self.filters: List[NostrFilter] = []
|
||||
self.auth_pubkey: Optional[str] = None # set if authenticated
|
||||
self._auth_challenge: Optional[str] = None
|
||||
self.filters: list[NostrFilter] = []
|
||||
self.auth_pubkey: str | None = None # set if authenticated
|
||||
self._auth_challenge: str | None = None
|
||||
self._auth_challenge_created_at = 0
|
||||
|
||||
self.event_validator = EventValidator(self.relay_id)
|
||||
|
||||
self.broadcast_event: Optional[
|
||||
Callable[[NostrClientConnection, NostrEvent], Awaitable[None]]
|
||||
] = None
|
||||
self.get_client_config: Optional[Callable[[], RelaySpec]] = None
|
||||
self.broadcast_event: (
|
||||
Callable[[NostrClientConnection, NostrEvent], Awaitable[None]] | None
|
||||
) = None
|
||||
self.get_client_config: Callable[[], RelaySpec] | None = None
|
||||
|
||||
async def start(self):
|
||||
await self.websocket.accept()
|
||||
|
|
@ -50,7 +51,7 @@ class NostrClientConnection:
|
|||
except Exception as e:
|
||||
logger.warning(e)
|
||||
|
||||
async def stop(self, reason: Optional[str]):
|
||||
async def stop(self, reason: str | None):
|
||||
message = reason if reason else "Server closed webocket"
|
||||
try:
|
||||
await self._send_msg(["NOTICE", message])
|
||||
|
|
@ -98,7 +99,7 @@ class NostrClientConnection:
|
|||
if self.broadcast_event:
|
||||
await self.broadcast_event(self, e)
|
||||
|
||||
async def _handle_message(self, data: List) -> List:
|
||||
async def _handle_message(self, data: list) -> list:
|
||||
if len(data) < 2:
|
||||
return []
|
||||
|
||||
|
|
@ -121,7 +122,9 @@ class NostrClientConnection:
|
|||
# Handle multiple filters in REQ message
|
||||
responses = []
|
||||
for filter_data in data[2:]:
|
||||
response = await self._handle_request(subscription_id, NostrFilter.parse_obj(filter_data))
|
||||
response = await self._handle_request(
|
||||
subscription_id, NostrFilter.parse_obj(filter_data)
|
||||
)
|
||||
responses.extend(response)
|
||||
return responses
|
||||
if message_type == NostrEventType.CLOSE:
|
||||
|
|
@ -133,7 +136,7 @@ class NostrClientConnection:
|
|||
|
||||
async def _handle_event(self, e: NostrEvent):
|
||||
logger.info(f"nostr event: [{e.kind}, {e.pubkey}, '{e.content}']")
|
||||
resp_nip20: List[Any] = ["OK", e.id]
|
||||
resp_nip20: list[Any] = ["OK", e.id]
|
||||
|
||||
if e.is_auth_response_event:
|
||||
valid, message = self.event_validator.validate_auth_event(
|
||||
|
|
@ -172,12 +175,12 @@ class NostrClientConnection:
|
|||
|
||||
if d_tag_value:
|
||||
deletion_filter = NostrFilter(
|
||||
kinds=[e.kind],
|
||||
kinds=[e.kind],
|
||||
authors=[e.pubkey],
|
||||
**{"#d": [d_tag_value]},
|
||||
until=e.created_at
|
||||
**{"#d": [d_tag_value]}, # type: ignore
|
||||
until=e.created_at,
|
||||
)
|
||||
|
||||
|
||||
await delete_events(self.relay_id, deletion_filter)
|
||||
if not e.is_ephemeral_event:
|
||||
await create_event(e)
|
||||
|
|
@ -201,7 +204,7 @@ class NostrClientConnection:
|
|||
raise Exception("Client not ready!")
|
||||
return self.get_client_config()
|
||||
|
||||
async def _send_msg(self, data: List):
|
||||
async def _send_msg(self, data: list):
|
||||
await self.websocket.send_text(json.dumps(data))
|
||||
|
||||
async def _handle_delete_event(self, event: NostrEvent):
|
||||
|
|
@ -214,7 +217,7 @@ class NostrClientConnection:
|
|||
|
||||
async def _handle_request(
|
||||
self, subscription_id: str, nostr_filter: NostrFilter
|
||||
) -> List:
|
||||
) -> list:
|
||||
if self.config.require_auth_filter:
|
||||
if not self.auth_pubkey:
|
||||
return [["AUTH", self._current_auth_challenge()]]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from typing import List
|
||||
|
||||
from ..crud import get_config_for_all_active_relays
|
||||
from .client_connection import NostrClientConnection
|
||||
from .event import NostrEvent
|
||||
|
|
@ -47,7 +45,7 @@ class NostrClientManager:
|
|||
def get_relay_config(self, relay_id: str) -> RelaySpec:
|
||||
return self._active_relays[relay_id]
|
||||
|
||||
def clients(self, relay_id: str) -> List[NostrClientConnection]:
|
||||
def clients(self, relay_id: str) -> list[NostrClientConnection]:
|
||||
if relay_id not in self._clients:
|
||||
self._clients[relay_id] = []
|
||||
return self._clients[relay_id]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import time
|
||||
from typing import Callable, Optional, Tuple
|
||||
from collections.abc import Callable
|
||||
|
||||
from ..crud import get_account, get_storage_for_public_key, prune_old_events
|
||||
from ..helpers import extract_domain
|
||||
|
|
@ -15,11 +15,11 @@ class EventValidator:
|
|||
self._last_event_timestamp = 0 # in hours
|
||||
self._event_count_per_timestamp = 0
|
||||
|
||||
self.get_client_config: Optional[Callable[[], RelaySpec]] = None
|
||||
self.get_client_config: Callable[[], RelaySpec] | None = None
|
||||
|
||||
async def validate_write(
|
||||
self, e: NostrEvent, publisher_pubkey: str
|
||||
) -> Tuple[bool, str]:
|
||||
) -> tuple[bool, str]:
|
||||
valid, message = self._validate_event(e)
|
||||
if not valid:
|
||||
return (valid, message)
|
||||
|
|
@ -34,8 +34,8 @@ class EventValidator:
|
|||
return True, ""
|
||||
|
||||
def validate_auth_event(
|
||||
self, e: NostrEvent, auth_challenge: Optional[str]
|
||||
) -> Tuple[bool, str]:
|
||||
self, e: NostrEvent, auth_challenge: str | None
|
||||
) -> tuple[bool, str]:
|
||||
valid, message = self._validate_event(e)
|
||||
if not valid:
|
||||
return (valid, message)
|
||||
|
|
@ -59,7 +59,7 @@ class EventValidator:
|
|||
raise Exception("EventValidator not ready!")
|
||||
return self.get_client_config()
|
||||
|
||||
def _validate_event(self, e: NostrEvent) -> Tuple[bool, str]:
|
||||
def _validate_event(self, e: NostrEvent) -> tuple[bool, str]:
|
||||
if self._exceeded_max_events_per_hour():
|
||||
return False, "Exceeded max events per hour limit'!"
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ class EventValidator:
|
|||
|
||||
async def _validate_storage(
|
||||
self, pubkey: str, event_size_bytes: int
|
||||
) -> Tuple[bool, str]:
|
||||
) -> tuple[bool, str]:
|
||||
if self.config.is_read_only_relay:
|
||||
return False, "Cannot write event, relay is read-only"
|
||||
|
||||
|
|
@ -124,7 +124,7 @@ class EventValidator:
|
|||
|
||||
return self._event_count_per_timestamp > self.config.max_events_per_hour
|
||||
|
||||
def _created_at_in_range(self, created_at: int) -> Tuple[bool, str]:
|
||||
def _created_at_in_range(self, created_at: int) -> tuple[bool, str]:
|
||||
current_time = round(time.time())
|
||||
if self.config.created_at_in_past != 0:
|
||||
if created_at < (current_time - self.config.created_at_in_past):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from .event import NostrEvent
|
||||
|
|
@ -12,10 +10,10 @@ class NostrFilter(BaseModel):
|
|||
ids: list[str] = []
|
||||
authors: list[str] = []
|
||||
kinds: list[int] = []
|
||||
subscription_id: Optional[str] = None
|
||||
since: Optional[int] = None
|
||||
until: Optional[int] = None
|
||||
limit: Optional[int] = None
|
||||
subscription_id: str | None = None
|
||||
since: int | None = None
|
||||
until: int | None = None
|
||||
limit: int | None = None
|
||||
|
||||
def matches(self, e: NostrEvent) -> bool:
|
||||
# todo: starts with
|
||||
|
|
@ -93,9 +91,12 @@ class NostrFilter(BaseModel):
|
|||
|
||||
if len(self.d):
|
||||
d_s = ",".join([f"'{d}'" for d in self.d])
|
||||
d_join = "INNER JOIN nostrrelay.event_tags d_tags ON nostrrelay.events.id = d_tags.event_id"
|
||||
d_join = (
|
||||
"INNER JOIN nostrrelay.event_tags d_tags "
|
||||
"ON nostrrelay.events.id = d_tags.event_id"
|
||||
)
|
||||
d_where = f" d_tags.value in ({d_s}) AND d_tags.name = 'd'"
|
||||
|
||||
|
||||
inner_joins.append(d_join)
|
||||
where.append(d_where)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
|
|
@ -100,11 +98,11 @@ class RelaySpec(RelayPublicSpec, WalletSpec, AuthSpec):
|
|||
|
||||
class NostrRelay(BaseModel):
|
||||
id: str
|
||||
user_id: Optional[str] = None
|
||||
user_id: str | None = None
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
pubkey: Optional[str] = None
|
||||
contact: Optional[str] = None
|
||||
description: str | None = None
|
||||
pubkey: str | None = None
|
||||
contact: str | None = None
|
||||
active: bool = False
|
||||
meta: RelaySpec = RelaySpec()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue