Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backports_lnbits_changes #8

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def callback(event: Event, 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:
print(f"Your private key: {client.private_key.bech32()}")

Expand Down Expand Up @@ -66,7 +66,7 @@ def callback(event: Event):
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)

pubkey_to_str = (
Expand Down
1 change: 1 addition & 0 deletions nostr/bech32.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

from enum import Enum


class Encoding(Enum):
"""Enumeration type to list the various supported encodings."""
BECH32 = 1
Expand Down
57 changes: 19 additions & 38 deletions nostr/client/client.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,33 @@
from typing import *
import ssl
import time
import base64
import json
import os
import base64

from ..event import Event
from ..relay_manager import RelayManager
from ..message_type import ClientMessageType
from ..key import PrivateKey, PublicKey
import ssl
import time
from typing import *

from ..event import EncryptedDirectMessage, Event, EventKind
from ..filter import Filter, Filters
from ..event import Event, EventKind, EncryptedDirectMessage
from ..relay_manager import RelayManager
from ..key import PrivateKey, PublicKey
from ..message_type import ClientMessageType
from ..relay_manager import RelayManager

# from aes import AESCipher
from . import cbc


class NostrClient:
relays = [
# "wss://eagerporpoise9.lnbits.com/nostrclient/api/v1/relay",
"wss://localhost:5001/nostrclient/api/v1/relay",
# "wss://nostr-pub.wellorder.net",
# "wss://relay.damus.io",
# "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-pub.wellorder.net",
"wss://nostr.zebedee.cloud",
"wss://nodestr.fmt.wiz.biz",
"wss://nostr.oxtr.dev",
] # ["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()
private_key: PrivateKey
public_key: PublicKey

def __init__(self, private_key: str = "", relays: List[str] = [], connect=True):
self.generate_keys(private_key)
def __init__(self, privatekey_hex: str = "", relays: List[str] = [], connect=True):
self.generate_keys(privatekey_hex)

if len(relays):
self.relays = relays
Expand All @@ -55,13 +44,9 @@ def connect(self):
def close(self):
self.relay_manager.close_connections()

def generate_keys(self, private_key: str = None):
if private_key.startswith("nsec"):
self.private_key = PrivateKey.from_nsec(private_key)
elif private_key:
self.private_key = PrivateKey(bytes.fromhex(private_key))
else:
self.private_key = PrivateKey() # generate random key
def generate_keys(self, privatekey_hex: str = None):
pk = bytes.fromhex(privatekey_hex) if privatekey_hex else None
self.private_key = PrivateKey(pk)
self.public_key = self.private_key.public_key

def post(self, message: str):
Expand All @@ -87,7 +72,6 @@ def get_post(
request = [ClientMessageType.REQUEST, subscription_id]
request.extend(filters.to_json_array())
message = json.dumps(request)
# print(message)
self.relay_manager.publish_message(message)

while True:
Expand All @@ -102,16 +86,14 @@ def dm(self, message: str, to_pubkey: PublicKey):
recipient_pubkey=to_pubkey.hex(), cleartext_content=message
)
self.private_key.sign_event(dm)
# print(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(
[
Filter(
kinds=[EventKind.ENCRYPTED_DIRECT_MESSAGE],
pubkey_refs=[sender_publickey.hex()],
**filter_kwargs,
)
]
)
Expand All @@ -122,7 +104,7 @@ def get_dm(self, sender_publickey: PublicKey, callback_func=None, filter_kwargs=
request.extend(filters.to_json_array())
message = json.dumps(request)
self.relay_manager.publish_message(message)
# print(message)

while True:
while self.relay_manager.message_pool.has_events():
event_msg = self.relay_manager.message_pool.get_event()
Expand Down Expand Up @@ -152,11 +134,10 @@ def subscribe(
while True:
while self.relay_manager.message_pool.has_events():
event_msg = self.relay_manager.message_pool.get_event()
print(event_msg.event.content)
if callback_events_func:
callback_events_func(event_msg)
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()
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Endless loop due to has_notice fixed.

if callback_notices_func:
callback_notices_func(event_msg)
while self.relay_manager.message_pool.has_eose_notices():
Expand Down
5 changes: 3 additions & 2 deletions nostr/event.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import time
import json
import time
from dataclasses import dataclass, field
from enum import IntEnum
from hashlib import sha256
from typing import List

from secp256k1 import PublicKey
from hashlib import sha256

from .message_type import ClientMessageType

Expand Down
9 changes: 5 additions & 4 deletions nostr/key.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import secrets
import base64
import secrets
from hashlib import sha256

import secp256k1
from cffi import FFI
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from hashlib import sha256
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

from . import bech32
from .delegation import Delegation
from .event import EncryptedDirectMessage, Event, EventKind
from . import bech32


class PublicKey:
Expand Down
3 changes: 2 additions & 1 deletion nostr/message_pool.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import json
from queue import Queue
from threading import Lock
from .message_type import RelayMessageType

from .event import Event
from .message_type import RelayMessageType


class EventMessage:
Expand Down
2 changes: 2 additions & 0 deletions nostr/pow.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import time

from .event import Event
from .key import PrivateKey


def zero_bits(b: int) -> int:
n = 0

Expand Down
34 changes: 21 additions & 13 deletions nostr/relay.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import time
from queue import Queue
from threading import Lock

from websocket import WebSocketApp

from .event import Event
from .filter import Filters
from .message_pool import MessagePool
Expand Down Expand Up @@ -33,26 +35,27 @@ def __init__(
self.subscriptions = subscriptions
self.connected: bool = False
self.reconnect: bool = True
self.shutdown: bool = False
self.error_counter: int = 0
self.error_threshold: int = 0
self.error_threshold: int = 100
self.num_received_events: int = 0
self.num_sent_events: int = 0
self.num_subscriptions: int = 0
self.ssl_options: dict = {}
self.proxy: dict = {}
self.lock = Lock()
self.queue = Queue()

def connect(self, ssl_options: dict = None, proxy: dict = None):
self.ws = WebSocketApp(
url,
self.url,
on_open=self._on_open,
on_message=self._on_message,
on_error=self._on_error,
on_close=self._on_close,
on_ping=self._on_ping,
on_pong=self._on_pong,
)

def connect(self, ssl_options: dict = None, proxy: dict = None):
self.ssl_options = ssl_options
self.proxy = proxy
if not self.connected:
Expand All @@ -66,6 +69,7 @@ def connect(self, ssl_options: dict = None, proxy: dict = None):

def close(self):
self.ws.close()
self.shutdown = True

def check_reconnect(self):
try:
Expand All @@ -74,7 +78,7 @@ def check_reconnect(self):
pass
self.connected = False
if self.reconnect:
time.sleep(1)
time.sleep(self.error_counter**2)
self.connect(self.ssl_options, self.proxy)

@property
Expand All @@ -85,12 +89,16 @@ def ping(self):
def publish(self, message: str):
self.queue.put(message)

def queue_worker(self):
def queue_worker(self, shutdown):
while True:
if self.connected:
message = self.queue.get()
self.num_sent_events += 1
self.ws.send(message)
try:
message = self.queue.get(timeout=1)
self.num_sent_events += 1
self.ws.send(message)
except:
if shutdown():
break
Comment on lines +95 to +101
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can shutdown worker now via shutdown()

else:
time.sleep(0.1)

Expand Down Expand Up @@ -123,6 +131,10 @@ def _on_open(self, class_obj):

def _on_close(self, class_obj, status_code, message):
self.connected = False
if self.error_threshold and self.error_counter > self.error_threshold:
pass
else:
self.check_reconnect()
Comment on lines +134 to +137
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reconnect on error

pass

def _on_message(self, class_obj, message: str):
Expand All @@ -133,10 +145,6 @@ def _on_message(self, class_obj, message: str):
def _on_error(self, class_obj, error):
self.connected = False
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):
return
Expand Down
20 changes: 15 additions & 5 deletions nostr/relay_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class RelayException(Exception):
class RelayManager:
def __init__(self) -> None:
self.relays: dict[str, Relay] = {}
self.threads: dict[str, threading.Thread] = {}
self.queue_threads: dict[str, threading.Thread] = {}
self.message_pool = MessagePool()

def add_relay(
Expand All @@ -25,7 +27,10 @@ def add_relay(
self.relays[url] = relay

def remove_relay(self, url: str):
self.relays[url].close()
self.relays.pop(url)
self.threads[url].join(timeout=1)
self.threads.pop(url)
Comment on lines +30 to +33
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove relays during runtime.


def add_subscription(self, id: str, filters: Filters):
for relay in self.relays.values():
Expand All @@ -37,16 +42,21 @@ def close_subscription(self, id: str):

def open_connections(self, ssl_options: dict = None, proxy: dict = None):
for relay in self.relays.values():
threading.Thread(
self.threads[relay.url] = threading.Thread(
target=relay.connect,
args=(ssl_options, proxy),
name=f"{relay.url}-thread",
daemon=True,
).start()
)
self.threads[relay.url].start()
Comment on lines +45 to +51
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We save all threads now.


threading.Thread(
target=relay.queue_worker, name=f"{relay.url}-queue", daemon=True
).start()
self.queue_threads[relay.url] = threading.Thread(
target=relay.queue_worker,
args=(lambda: relay.shutdown,),
name=f"{relay.url}-queue",
daemon=True,
)
self.queue_threads[relay.url].start()
Comment on lines +53 to +59
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also save all queue worker threads and add a shutdown argument


def close_connections(self):
for relay in self.relays.values():
Expand Down
1 change: 1 addition & 0 deletions nostr/subscription.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .filter import Filters


class Subscription:
def __init__(self, id: str, filters: Filters=None) -> None:
self.id = id
Expand Down