Merge pull request #4 from vicariousdrama/20231029-calle-Backports_lnbits_changes

Changes from https://github.com/callebtc/python-nostr/pull/8
This commit is contained in:
Vic
2023-10-29 15:06:03 -04:00
committed by GitHub
10 changed files with 74 additions and 69 deletions

View File

@@ -27,7 +27,7 @@ async def dm():
f"\nFrom {event.public_key[:3]}..{event.public_key[-3:]}: {decrypted_content}" f"\nFrom {event.public_key[:3]}..{event.public_key[-3:]}: {decrypted_content}"
) )
client = NostrClient(private_key=pk) client = NostrClient(privatekey_hex=pk)
if not pk: if not pk:
print(f"Your private key: {client.private_key.bech32()}") print(f"Your private key: {client.private_key.bech32()}")
@@ -66,7 +66,7 @@ async def post():
f"\nFrom {event.public_key[:3]}..{event.public_key[-3:]}: {event.content}" f"\nFrom {event.public_key[:3]}..{event.public_key[-3:]}: {event.content}"
) )
sender_client = NostrClient(private_key=pk) sender_client = NostrClient(privatekey_hex=pk)
# await asyncio.sleep(1) # await asyncio.sleep(1)
pubkey_to_str = ( pubkey_to_str = (

View File

@@ -23,6 +23,7 @@
from enum import Enum from enum import Enum
class Encoding(Enum): class Encoding(Enum):
"""Enumeration type to list the various supported encodings.""" """Enumeration type to list the various supported encodings."""
BECH32 = 1 BECH32 = 1

View File

@@ -1,19 +1,15 @@
from typing import * import base64
import ssl
import time
import json import json
import os import os
import base64 import ssl
import time
from ..event import Event from typing import *
from ..relay_manager import RelayManager
from ..message_type import ClientMessageType
from ..key import PrivateKey, PublicKey
from ..event import EncryptedDirectMessage, Event, EventKind
from ..filter import Filter, Filters from ..filter import Filter, Filters
from ..event import Event, EventKind, EncryptedDirectMessage from ..key import PrivateKey, PublicKey
from ..relay_manager import RelayManager
from ..message_type import ClientMessageType from ..message_type import ClientMessageType
from ..relay_manager import RelayManager
# from aes import AESCipher # from aes import AESCipher
from . import cbc from . import cbc
@@ -21,24 +17,17 @@ from . import cbc
class NostrClient: class NostrClient:
relays = [ relays = [
# "wss://eagerporpoise9.lnbits.com/nostrclient/api/v1/relay", "wss://nostr-pub.wellorder.net",
"wss://localhost:5001/nostrclient/api/v1/relay", "wss://nostr.zebedee.cloud",
# "wss://nostr-pub.wellorder.net", "wss://nodestr.fmt.wiz.biz",
# "wss://relay.damus.io", "wss://nostr.oxtr.dev",
# "wss://nostr.zebedee.cloud",
# "wss://relay.snort.social",
# "wss://nostr.fmt.wiz.biz",
# "wss://nos.lol",
# "wss://nostr.oxtr.dev",
# "wss://relay.current.fyi",
# "wss://relay.snort.social",
] # ["wss://nostr.oxtr.dev"] # ["wss://relay.nostr.info"] "wss://nostr-pub.wellorder.net" "ws://91.237.88.218:2700", "wss://nostrrr.bublina.eu.org", ""wss://nostr-relay.freeberty.net"", , "wss://nostr.oxtr.dev", "wss://relay.nostr.info", "wss://nostr-pub.wellorder.net" , "wss://relayer.fiatjaf.com", "wss://nodestr.fmt.wiz.biz/", "wss://no.str.cr" ] # ["wss://nostr.oxtr.dev"] # ["wss://relay.nostr.info"] "wss://nostr-pub.wellorder.net" "ws://91.237.88.218:2700", "wss://nostrrr.bublina.eu.org", ""wss://nostr-relay.freeberty.net"", , "wss://nostr.oxtr.dev", "wss://relay.nostr.info", "wss://nostr-pub.wellorder.net" , "wss://relayer.fiatjaf.com", "wss://nodestr.fmt.wiz.biz/", "wss://no.str.cr"
relay_manager = RelayManager() relay_manager = RelayManager()
private_key: PrivateKey private_key: PrivateKey
public_key: PublicKey public_key: PublicKey
def __init__(self, private_key: str = "", relays: List[str] = [], connect=True): def __init__(self, privatekey_hex: str = "", relays: List[str] = [], connect=True):
self.generate_keys(private_key) self.generate_keys(privatekey_hex)
if len(relays): if len(relays):
self.relays = relays self.relays = relays
@@ -55,13 +44,9 @@ class NostrClient:
def close(self): def close(self):
self.relay_manager.close_connections() self.relay_manager.close_connections()
def generate_keys(self, private_key: str = None): def generate_keys(self, privatekey_hex: str = None):
if private_key.startswith("nsec"): pk = bytes.fromhex(privatekey_hex) if privatekey_hex else None
self.private_key = PrivateKey.from_nsec(private_key) self.private_key = PrivateKey(pk)
elif private_key:
self.private_key = PrivateKey(bytes.fromhex(private_key))
else:
self.private_key = PrivateKey() # generate random key
self.public_key = self.private_key.public_key self.public_key = self.private_key.public_key
def post(self, message: str): def post(self, message: str):
@@ -87,7 +72,6 @@ class NostrClient:
request = [ClientMessageType.REQUEST, subscription_id] request = [ClientMessageType.REQUEST, subscription_id]
request.extend(filters.to_json_array()) request.extend(filters.to_json_array())
message = json.dumps(request) message = json.dumps(request)
# print(message)
self.relay_manager.publish_message(message) self.relay_manager.publish_message(message)
while True: while True:
@@ -102,16 +86,14 @@ class NostrClient:
recipient_pubkey=to_pubkey.hex(), cleartext_content=message recipient_pubkey=to_pubkey.hex(), cleartext_content=message
) )
self.private_key.sign_event(dm) self.private_key.sign_event(dm)
# print(dm)
self.relay_manager.publish_event(dm) self.relay_manager.publish_event(dm)
def get_dm(self, sender_publickey: PublicKey, callback_func=None, filter_kwargs={}): def get_dm(self, sender_publickey: PublicKey, callback_func=None):
filters = Filters( filters = Filters(
[ [
Filter( Filter(
kinds=[EventKind.ENCRYPTED_DIRECT_MESSAGE], kinds=[EventKind.ENCRYPTED_DIRECT_MESSAGE],
pubkey_refs=[sender_publickey.hex()], pubkey_refs=[sender_publickey.hex()],
**filter_kwargs,
) )
] ]
) )
@@ -122,7 +104,7 @@ class NostrClient:
request.extend(filters.to_json_array()) request.extend(filters.to_json_array())
message = json.dumps(request) message = json.dumps(request)
self.relay_manager.publish_message(message) self.relay_manager.publish_message(message)
# print(message)
while True: while True:
while self.relay_manager.message_pool.has_events(): while self.relay_manager.message_pool.has_events():
event_msg = self.relay_manager.message_pool.get_event() event_msg = self.relay_manager.message_pool.get_event()
@@ -156,7 +138,7 @@ class NostrClient:
if callback_events_func: if callback_events_func:
callback_events_func(event_msg) callback_events_func(event_msg)
while self.relay_manager.message_pool.has_notices(): while self.relay_manager.message_pool.has_notices():
event_msg = self.relay_manager.message_pool.has_notices() event_msg = self.relay_manager.message_pool.get_notice()
if callback_notices_func: if callback_notices_func:
callback_notices_func(event_msg) callback_notices_func(event_msg)
while self.relay_manager.message_pool.has_eose_notices(): while self.relay_manager.message_pool.has_eose_notices():

View File

@@ -1,10 +1,11 @@
import time
import json import json
import time
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import IntEnum from enum import IntEnum
from typing import List
from secp256k1 import PublicKey
from hashlib import sha256 from hashlib import sha256
from typing import List
from secp256k1 import PublicKey
from .message_type import ClientMessageType from .message_type import ClientMessageType

View File

@@ -1,14 +1,16 @@
import secrets
import base64 import base64
import secp256k1 import secrets
from cffi import FFI
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from hashlib import sha256 from hashlib import sha256
import secp256k1
from cffi import FFI
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from . import bech32
from .delegation import Delegation from .delegation import Delegation
from .event import EncryptedDirectMessage, Event, EventKind from .event import EncryptedDirectMessage, Event, EventKind
from . import bech32
class PublicKey: class PublicKey:

View File

@@ -1,8 +1,9 @@
import json import json
from queue import Queue from queue import Queue
from threading import Lock from threading import Lock
from .message_type import RelayMessageType
from .event import Event from .event import Event
from .message_type import RelayMessageType
class EventMessage: class EventMessage:

View File

@@ -1,7 +1,9 @@
import time import time
from .event import Event from .event import Event
from .key import PrivateKey from .key import PrivateKey
def zero_bits(b: int) -> int: def zero_bits(b: int) -> int:
n = 0 n = 0

View File

@@ -2,7 +2,9 @@ import json
import time import time
from queue import Queue from queue import Queue
from threading import Lock from threading import Lock
from websocket import WebSocketApp, WebSocketConnectionClosedException
from websocket import WebSocketApp
from .event import Event from .event import Event
from .filter import Filters from .filter import Filters
from .message_pool import MessagePool from .message_pool import MessagePool
@@ -33,8 +35,9 @@ class Relay:
self.subscriptions = subscriptions self.subscriptions = subscriptions
self.connected: bool = False self.connected: bool = False
self.reconnect: bool = True self.reconnect: bool = True
self.shutdown: bool = False
self.error_counter: int = 0 self.error_counter: int = 0
self.error_threshold: int = 0 self.error_threshold: int = 100
self.num_received_events: int = 0 self.num_received_events: int = 0
self.num_sent_events: int = 0 self.num_sent_events: int = 0
self.num_subscriptions: int = 0 self.num_subscriptions: int = 0
@@ -42,8 +45,10 @@ class Relay:
self.proxy: dict = {} self.proxy: dict = {}
self.lock = Lock() self.lock = Lock()
self.queue = Queue() self.queue = Queue()
def connect(self, ssl_options: dict = None, proxy: dict = None):
self.ws = WebSocketApp( self.ws = WebSocketApp(
url, self.url,
on_open=self._on_open, on_open=self._on_open,
on_message=self._on_message, on_message=self._on_message,
on_error=self._on_error, on_error=self._on_error,
@@ -51,8 +56,6 @@ class Relay:
on_ping=self._on_ping, on_ping=self._on_ping,
on_pong=self._on_pong, on_pong=self._on_pong,
) )
def connect(self, ssl_options: dict = None, proxy: dict = None):
self.ssl_options = ssl_options self.ssl_options = ssl_options
self.proxy = proxy self.proxy = proxy
if not self.connected: if not self.connected:
@@ -66,6 +69,7 @@ class Relay:
def close(self): def close(self):
self.ws.close() self.ws.close()
self.shutdown = True
def check_reconnect(self): def check_reconnect(self):
try: try:
@@ -74,7 +78,7 @@ class Relay:
pass pass
self.connected = False self.connected = False
if self.reconnect: if self.reconnect:
time.sleep(1) time.sleep(self.error_counter**2)
self.connect(self.ssl_options, self.proxy) self.connect(self.ssl_options, self.proxy)
@property @property
@@ -85,15 +89,16 @@ class Relay:
def publish(self, message: str): def publish(self, message: str):
self.queue.put(message) self.queue.put(message)
def queue_worker(self): def queue_worker(self, shutdown):
while True: while True:
if self.connected: if self.connected:
message = self.queue.get()
self.num_sent_events += 1
try: try:
message = self.queue.get(timeout=1)
self.ws.send(message) self.ws.send(message)
except WebSocketConnectionClosedException as wscce: self.num_sent_events += 1
self._on_error(None, wscce) except:
if shutdown():
break
else: else:
time.sleep(0.1) time.sleep(0.1)
@@ -126,6 +131,10 @@ class Relay:
def _on_close(self, class_obj, status_code, message): def _on_close(self, class_obj, status_code, message):
self.connected = False self.connected = False
if self.error_threshold and self.error_counter > self.error_threshold:
pass
else:
self.check_reconnect()
pass pass
def _on_message(self, class_obj, message: str): def _on_message(self, class_obj, message: str):
@@ -136,10 +145,6 @@ class Relay:
def _on_error(self, class_obj, error): def _on_error(self, class_obj, error):
self.connected = False self.connected = False
self.error_counter += 1 self.error_counter += 1
if self.error_threshold and self.error_counter > self.error_threshold:
pass
else:
self.check_reconnect()
def _on_ping(self, class_obj, message): def _on_ping(self, class_obj, message):
return return

View File

@@ -15,6 +15,8 @@ class RelayException(Exception):
class RelayManager: class RelayManager:
def __init__(self) -> None: def __init__(self) -> None:
self.relays: dict[str, Relay] = {} self.relays: dict[str, Relay] = {}
self.threads: dict[str, threading.Thread] = {}
self.queue_threads: dict[str, threading.Thread] = {}
self.message_pool = MessagePool() self.message_pool = MessagePool()
def add_relay( def add_relay(
@@ -25,7 +27,10 @@ class RelayManager:
self.relays[url] = relay self.relays[url] = relay
def remove_relay(self, url: str): def remove_relay(self, url: str):
self.relays[url].close()
self.relays.pop(url) self.relays.pop(url)
self.threads[url].join(timeout=1)
self.threads.pop(url)
def add_subscription(self, id: str, filters: Filters): def add_subscription(self, id: str, filters: Filters):
for relay in self.relays.values(): for relay in self.relays.values():
@@ -37,16 +42,21 @@ class RelayManager:
def open_connections(self, ssl_options: dict = None, proxy: dict = None): def open_connections(self, ssl_options: dict = None, proxy: dict = None):
for relay in self.relays.values(): for relay in self.relays.values():
threading.Thread( self.threads[relay.url] = threading.Thread(
target=relay.connect, target=relay.connect,
args=(ssl_options, proxy), args=(ssl_options, proxy),
name=f"{relay.url}-thread", name=f"{relay.url}-thread",
daemon=True, daemon=True,
).start() )
self.threads[relay.url].start()
threading.Thread( self.queue_threads[relay.url] = threading.Thread(
target=relay.queue_worker, name=f"{relay.url}-queue", daemon=True target=relay.queue_worker,
).start() args=(lambda: relay.shutdown,),
name=f"{relay.url}-queue",
daemon=True,
)
self.queue_threads[relay.url].start()
def close_connections(self): def close_connections(self):
for relay in self.relays.values(): for relay in self.relays.values():

View File

@@ -1,5 +1,6 @@
from .filter import Filters from .filter import Filters
class Subscription: class Subscription:
def __init__(self, id: str, filters: Filters=None) -> None: def __init__(self, id: str, filters: Filters=None) -> None:
self.id = id self.id = id