From 77af490acfa81ff3178562aa446dc4791d69c503 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 26 Dec 2022 15:23:14 +0100 Subject: [PATCH 01/40] client --- main.py | 57 +++++++++ nostr/client/__init__.py | 0 nostr/client/cbc.py | 41 +++++++ nostr/client/client.py | 165 +++++++++++++++++++++++++ nostr/filter.py | 30 ++--- nostr/relay.py | 47 ++++--- poetry.lock | 259 +++++++++++++++++++++++++++++++++++++++ pyproject.toml | 19 +++ 8 files changed, 587 insertions(+), 31 deletions(-) create mode 100644 main.py create mode 100644 nostr/client/__init__.py create mode 100644 nostr/client/cbc.py create mode 100644 nostr/client/client.py create mode 100644 poetry.lock create mode 100644 pyproject.toml diff --git a/main.py b/main.py new file mode 100644 index 0000000..8da93c2 --- /dev/null +++ b/main.py @@ -0,0 +1,57 @@ + +from nostr.client.client import NostrClient +from nostr.event import Event +from nostr.key import PublicKey +import asyncio + +async def dm(): + print("This is an example NIP-04 DM flow") + pk = input("Enter your privatekey to post from (enter nothing for a random one): ") + + def callback(event:Event, decrypted_content): + print(f"From {event.public_key[:3]}..{event.public_key[-3:]}: {decrypted_content}") + + client = NostrClient( + privatekey_hex=pk + ) + await asyncio.sleep(1) + + + import threading + t = threading.Thread(target=client.get_dm, args=(client.public_key,callback,)) + t.start() + + to_pubk_hex = input("Enter other pubkey to post to (enter nothing to DM yourself): ") or client.public_key.hex() + print(f"Subscribing to DMs to {to_pubk_hex}") + while True: + msg = input("\nEnter message: ") + client.dm(msg, PublicKey(bytes.fromhex(to_pubk_hex))) + +async def post(): + print("This posts and reads a nostr note") + pk = input("Enter your privatekey to post from (enter nothing for a random one): ") + + def callback(event:Event): + print(f"From {event.public_key[:3]}..{event.public_key[-3:]}: {event.content}") + + sender_client = NostrClient( + privatekey_hex=pk + ) + await asyncio.sleep(1) + + to_pubk_hex = input("Enter other pubkey (enter nothing to read your own posts): ") or sender_client.public_key.hex() + print(f"Subscribing to posts by {to_pubk_hex}") + to_pubk = PublicKey(bytes.fromhex(to_pubk_hex)) + + import threading + t = threading.Thread(target=sender_client.get_post, args=(to_pubk, callback,)) + t.start() + + + while True: + msg = input("\nEnter post: ") + sender_client.post(msg) + +asyncio.run(post()) + + diff --git a/nostr/client/__init__.py b/nostr/client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nostr/client/cbc.py b/nostr/client/cbc.py new file mode 100644 index 0000000..a41dbc0 --- /dev/null +++ b/nostr/client/cbc.py @@ -0,0 +1,41 @@ + +from Cryptodome import Random +from Cryptodome.Cipher import AES + +plain_text = "This is the text to encrypts" + +# encrypted = "7mH9jq3K9xNfWqIyu9gNpUz8qBvGwsrDJ+ACExdV1DvGgY8q39dkxVKeXD7LWCDrPnoD/ZFHJMRMis8v9lwHfNgJut8EVTMuJJi8oTgJevOBXl+E+bJPwej9hY3k20rgCQistNRtGHUzdWyOv7S1tg==".encode() +# iv = "GzDzqOVShWu3Pl2313FBpQ==".encode() + +key = bytes.fromhex("3aa925cb69eb613e2928f8a18279c78b1dca04541dfd064df2eda66b59880795") + +BLOCK_SIZE = 16 + +class AESCipher(object): + """This class is compatible with crypto.createCipheriv('aes-256-cbc') + + """ + def __init__(self, key=None): + self.key = key + + def pad(self, data): + length = BLOCK_SIZE - (len(data) % BLOCK_SIZE) + return data + (chr(length) * length).encode() + + def unpad(self, data): + return data[: -(data[-1] if type(data[-1]) == int else ord(data[-1]))] + + def encrypt(self, plain_text): + cipher = AES.new(self.key, AES.MODE_CBC) + b = plain_text.encode("UTF-8") + return cipher.iv, cipher.encrypt(self.pad(b)) + + def decrypt(self, iv, enc_text): + cipher = AES.new(self.key, AES.MODE_CBC, iv=iv) + return self.unpad(cipher.decrypt(enc_text).decode("UTF-8")) + +if __name__ == "__main__": + aes = AESCipher(key=key) + iv, enc_text = aes.encrypt(plain_text) + dec_text = aes.decrypt(iv, enc_text) + print(dec_text) \ No newline at end of file diff --git a/nostr/client/client.py b/nostr/client/client.py new file mode 100644 index 0000000..57525cf --- /dev/null +++ b/nostr/client/client.py @@ -0,0 +1,165 @@ +from typing import * +import ssl +import time +import json +import os +import base64 + +from nostr.event import Event +from nostr.relay_manager import RelayManager +from nostr.message_type import ClientMessageType +from nostr.key import PrivateKey, PublicKey + +from nostr.filter import Filter, Filters +from nostr.event import Event, EventKind +from nostr.relay_manager import RelayManager +from nostr.message_type import ClientMessageType + +# from aes import AESCipher +from . import cbc + + +class NostrClient: + relays = [ + # "wss://nostr-pub.wellorder.net" + "wss://nostr.zebedee.cloud", + # "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() + private_key: PrivateKey + public_key: PublicKey + + def __init__(self, privatekey_hex: str = "", relays: List[str] = []): + self.generate_keys(privatekey_hex) + + if len(relays): + self.relays = relays + + for relay in self.relays: + self.relay_manager.add_relay(relay) + self.relay_manager.open_connections( + {"cert_reqs": ssl.CERT_NONE} + ) # NOTE: This disables ssl certificate verification + + def close(self): + self.relay_manager.close_connections() + + 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 + print(f"Private key: {self.private_key.bech32()} ({self.private_key.hex()})") + print(f"Public key: {self.public_key.bech32()} ({self.public_key.hex()})") + + def post(self, message: str): + event = Event(self.public_key.hex(), message, kind=EventKind.TEXT_NOTE) + event.sign(self.private_key.hex()) + message = json.dumps([ClientMessageType.EVENT, event.to_json_object()]) + # print("Publishing message:") + # print(message) + self.relay_manager.publish_message(message) + + def get_post(self, sender_publickey: PublicKey, callback_func=None): + filters = Filters( + [Filter(authors=[sender_publickey.hex()], kinds=[EventKind.TEXT_NOTE])] + ) + subscription_id = os.urandom(4).hex() + self.relay_manager.add_subscription(subscription_id, filters) + + request = [ClientMessageType.REQUEST, subscription_id] + request.extend(filters.to_json_array()) + message = json.dumps(request) + # print("Subscribing to events:") + # print(message) + self.relay_manager.publish_message(message) + + 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_func: + callback_func(event_msg.event) + time.sleep(0.1) + + def dm(self, message: str, to_pubkey: PublicKey): + + shared_secret = self.private_key.compute_shared_secret( + to_pubkey.hex() + ) + + # print("shared secret: ", shared_secret.hex()) + # print("plain text:", message) + aes = cbc.AESCipher(key=shared_secret) + iv, enc_text = aes.encrypt(message) + # print("encrypt iv: ", iv) + content = f"{base64.b64encode(enc_text).decode('utf-8')}?iv={base64.b64encode(iv).decode('utf-8')}" + + + event = Event( + self.public_key.hex(), + content, + tags=[["p", to_pubkey.hex()]], + kind=EventKind.ENCRYPTED_DIRECT_MESSAGE, + ) + event.sign(self.private_key.hex()) + event_message = json.dumps([ClientMessageType.EVENT, event.to_json_object()]) + # print("DM message:") + # print(event_message) + + time.sleep(1) + self.relay_manager.publish_message(event_message) + + def get_dm(self, sender_publickey: PublicKey, callback_func=None): + filters = Filters( + [ + Filter( + kinds=[EventKind.ENCRYPTED_DIRECT_MESSAGE], + tags={"#p": [sender_publickey.hex()]}, + ) + ] + ) + subscription_id = os.urandom(4).hex() + self.relay_manager.add_subscription(subscription_id, filters) + + request = [ClientMessageType.REQUEST, subscription_id] + request.extend(filters.to_json_array()) + message = json.dumps(request) + # print("Subscribing to events:") + # print(message) + self.relay_manager.publish_message(message) + + while True: + while self.relay_manager.message_pool.has_events(): + event_msg = self.relay_manager.message_pool.get_event() + if "?iv=" in event_msg.event.content: + try: + shared_secret = self.private_key.compute_shared_secret( + event_msg.event.public_key + ) + # print("shared secret: ", shared_secret.hex()) + # print("plain text:", message) + aes = cbc.AESCipher(key=shared_secret) + enc_text_b64, iv_b64 = event_msg.event.content.split("?iv=") + iv = base64.decodebytes(iv_b64.encode("utf-8")) + enc_text = base64.decodebytes(enc_text_b64.encode("utf-8")) + # print("decrypt iv: ", iv) + dec_text = aes.decrypt(iv, enc_text) + # print(f"From {event_msg.event.public_key[:5]}...: {dec_text}") + if callback_func: + callback_func(event_msg.event, dec_text) + except: + pass + # else: + # print(f"\nFrom {event_msg.event.public_key[:5]}...: {event_msg.event.content}") + break + time.sleep(0.1) + + async def subscribe(self): + while True: + while self.relay_manager.message_pool.has_events(): + event_msg = self.relay_manager.message_pool.get_event() + print(event_msg.event.content) + break + time.sleep(0.1) + diff --git a/nostr/filter.py b/nostr/filter.py index 1233249..32b94a3 100644 --- a/nostr/filter.py +++ b/nostr/filter.py @@ -1,16 +1,18 @@ from collections import UserList from .event import Event + class Filter: def __init__( - self, - ids: "list[str]"=None, - kinds: "list[int]"=None, - authors: "list[str]"=None, - since: int=None, - until: int=None, - tags: "dict[str, list[str]]"=None, - limit: int=None) -> None: + self, + ids: "list[str]" = None, + kinds: "list[int]" = None, + authors: "list[str]" = None, + since: int = None, + until: int = None, + tags: "dict[str, list[str]]" = None, + limit: int = None, + ) -> None: self.IDs = ids self.kinds = kinds self.authors = authors @@ -33,21 +35,21 @@ def matches(self, event: Event) -> bool: if self.tags != None and len(event.tags) == 0: return False if self.tags != None: - e_tag_identifiers = [e_tag[0] for e_tag in event.tags] + e_tag_identifiers = [e_tag[0] for e_tag in event.tags] for f_tag, f_tag_values in self.tags.items(): if f_tag[1:] not in e_tag_identifiers: return False for e_tag in event.tags: if e_tag[1] not in f_tag_values: return False - + return True def to_json_object(self) -> dict: res = {} if self.IDs != None: res["ids"] = self.IDs - if self.kinds != None: + if self.kinds != None: res["kinds"] = self.kinds if self.authors != None: res["authors"] = self.authors @@ -62,9 +64,10 @@ def to_json_object(self) -> dict: res["limit"] = self.limit return res - + + class Filters(UserList): - def __init__(self, initlist: "list[Filter]"=[]) -> None: + def __init__(self, initlist: "list[Filter]" = []) -> None: super().__init__(initlist) self.data: "list[Filter]" @@ -76,4 +79,3 @@ def match(self, event: Event): def to_json_array(self) -> list: return [filter.to_json_object() for filter in self.data] - \ No newline at end of file diff --git a/nostr/relay.py b/nostr/relay.py index e118229..ad01ff6 100644 --- a/nostr/relay.py +++ b/nostr/relay.py @@ -7,24 +7,24 @@ from .message_type import RelayMessageType from .subscription import Subscription + class RelayPolicy: - def __init__(self, should_read: bool=True, should_write: bool=True) -> None: + def __init__(self, should_read: bool = True, should_write: bool = True) -> None: self.should_read = should_read self.should_write = should_write def to_json_object(self) -> dict[str, bool]: - return { - "read": self.should_read, - "write": self.should_write - } + return {"read": self.should_read, "write": self.should_write} + class Relay: def __init__( - self, - url: str, - policy: RelayPolicy, - message_pool: MessagePool, - subscriptions: dict[str, Subscription]={}) -> None: + self, + url: str, + policy: RelayPolicy, + message_pool: MessagePool, + subscriptions: dict[str, Subscription] = {}, + ) -> None: self.url = url self.policy = policy self.message_pool = message_pool @@ -35,9 +35,10 @@ def __init__( on_open=self._on_open, on_message=self._on_message, on_error=self._on_error, - on_close=self._on_close) + on_close=self._on_close, + ) - def connect(self, ssl_options: dict=None): + def connect(self, ssl_options: dict = None): self.ws.run_forever(sslopt=ssl_options) def close(self): @@ -63,7 +64,10 @@ def to_json_object(self) -> dict: return { "url": self.url, "policy": self.policy.to_json_object(), - "subscriptions": [subscription.to_json_object() for subscription in self.subscriptions.values()] + "subscriptions": [ + subscription.to_json_object() + for subscription in self.subscriptions.values() + ], } def _on_open(self, class_obj): @@ -75,12 +79,13 @@ def _on_close(self, class_obj, status_code, message): def _on_message(self, class_obj, message: str): if self._is_valid_message(message): self.message_pool.add_message(message, self.url) - + def _on_error(self, class_obj, error): pass def _is_valid_message(self, message: str) -> bool: - if not message or message[0] != '[' or message[-1] != ']': + message = message.strip("\n") + if not message or message[0] != "[" or message[-1] != "]": return False message_json = json.loads(message) @@ -90,14 +95,22 @@ def _is_valid_message(self, message: str) -> bool: if message_type == RelayMessageType.EVENT: if not len(message_json) == 3: return False - + subscription_id = message_json[1] with self.lock: if subscription_id not in self.subscriptions: return False e = message_json[2] - event = Event(e['pubkey'], e['content'], e['created_at'], e['kind'], e['tags'], e['id'], e['sig']) + event = Event( + e["pubkey"], + e["content"], + e["created_at"], + e["kind"], + e["tags"], + e["id"], + e["sig"], + ) if not event.verify(): return False diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..7bcba2b --- /dev/null +++ b/poetry.lock @@ -0,0 +1,259 @@ +[[package]] +name = "black" +version = "22.12.0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" + +[[package]] +name = "importlib-metadata" +version = "5.2.0" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pathspec" +version = "0.10.3" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "platformdirs" +version = "2.6.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] +test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] + +[[package]] +name = "pycryptodomex" +version = "3.16.0" +description = "Cryptographic library for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "typed-ast" +version = "1.5.4" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "websocket-client" +version = "1.3.3" +description = "WebSocket client for Python with low level API options" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"] +optional = ["python-socks", "wsaccel"] +test = ["websockets"] + +[[package]] +name = "zipp" +version = "3.11.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.7" +content-hash = "b4819f5f4403092e6aafe15d38b9a0850eff2c7b5f9417b2cd0ad5b91c913bcd" + +[metadata.files] +black = [ + {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, + {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, + {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, + {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, + {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, + {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, + {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, + {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, + {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, + {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, + {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, + {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, +] +click = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] +colorama = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] +importlib-metadata = [ + {file = "importlib_metadata-5.2.0-py3-none-any.whl", hash = "sha256:0eafa39ba42bf225fc00e67f701d71f85aead9f878569caf13c3724f704b970f"}, + {file = "importlib_metadata-5.2.0.tar.gz", hash = "sha256:404d48d62bba0b7a77ff9d405efd91501bef2e67ff4ace0bed40a0cf28c3c7cd"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +pathspec = [ + {file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"}, + {file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"}, +] +platformdirs = [ + {file = "platformdirs-2.6.0-py3-none-any.whl", hash = "sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca"}, + {file = "platformdirs-2.6.0.tar.gz", hash = "sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e"}, +] +pycryptodomex = [ + {file = "pycryptodomex-3.16.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b3d04c00d777c36972b539fb79958790126847d84ec0129fce1efef250bfe3ce"}, + {file = "pycryptodomex-3.16.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e5a670919076b71522c7d567a9043f66f14b202414a63c3a078b5831ae342c03"}, + {file = "pycryptodomex-3.16.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:ce338a9703f54b2305a408fc9890eb966b727ce72b69f225898bb4e9d9ed3f1f"}, + {file = "pycryptodomex-3.16.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:a1c0ae7123448ecb034c75c713189cb00ebe2d415b11682865b6c54d200d9c93"}, + {file = "pycryptodomex-3.16.0-cp27-cp27m-win32.whl", hash = "sha256:8851585ff19871e5d69e1790f4ca5f6fd1699d6b8b14413b472a4c0dbc7ea780"}, + {file = "pycryptodomex-3.16.0-cp27-cp27m-win_amd64.whl", hash = "sha256:8dd2d9e3c617d0712ed781a77efd84ea579e76c5f9b2a4bc0b684ebeddf868b2"}, + {file = "pycryptodomex-3.16.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:2ad9bb86b355b6104796567dd44c215b3dc953ef2fae5e0bdfb8516731df92cf"}, + {file = "pycryptodomex-3.16.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:e25a2f5667d91795f9417cb856f6df724ccdb0cdd5cbadb212ee9bf43946e9f8"}, + {file = "pycryptodomex-3.16.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:b0789a8490114a2936ed77c87792cfe77582c829cb43a6d86ede0f9624ba8aa3"}, + {file = "pycryptodomex-3.16.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0da835af786fdd1c9930994c78b23e88d816dc3f99aa977284a21bbc26d19735"}, + {file = "pycryptodomex-3.16.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:22aed0868622d95179217c298e37ed7410025c7b29dac236d3230617d1e4ed56"}, + {file = "pycryptodomex-3.16.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1619087fb5b31510b0b0b058a54f001a5ffd91e6ffee220d9913064519c6a69d"}, + {file = "pycryptodomex-3.16.0-cp35-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:70288d9bfe16b2fd0d20b6c365db614428f1bcde7b20d56e74cf88ade905d9eb"}, + {file = "pycryptodomex-3.16.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7993d26dae4d83b8f4ce605bb0aecb8bee330bb3c95475ef06f3694403621e71"}, + {file = "pycryptodomex-3.16.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:1cda60207be8c1cf0b84b9138f9e3ca29335013d2b690774a5e94678ff29659a"}, + {file = "pycryptodomex-3.16.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:04610536921c1ec7adba158ef570348550c9f3a40bc24be9f8da2ef7ab387981"}, + {file = "pycryptodomex-3.16.0-cp35-abi3-win32.whl", hash = "sha256:daa67f5ebb6fbf1ee9c90decaa06ca7fc88a548864e5e484d52b0920a57fe8a5"}, + {file = "pycryptodomex-3.16.0-cp35-abi3-win_amd64.whl", hash = "sha256:231dc8008cbdd1ae0e34645d4523da2dbc7a88c325f0d4a59635a86ee25b41dd"}, + {file = "pycryptodomex-3.16.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:4dbbe18cc232b5980c7633972ae5417d0df76fe89e7db246eefd17ef4d8e6d7a"}, + {file = "pycryptodomex-3.16.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:893f8a97d533c66cc3a56e60dd3ed40a3494ddb4aafa7e026429a08772f8a849"}, + {file = "pycryptodomex-3.16.0-pp27-pypy_73-win32.whl", hash = "sha256:6a465e4f856d2a4f2a311807030c89166529ccf7ccc65bef398de045d49144b6"}, + {file = "pycryptodomex-3.16.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba57ac7861fd2c837cdb33daf822f2a052ff57dd769a2107807f52a36d0e8d38"}, + {file = "pycryptodomex-3.16.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f2b971a7b877348a27dcfd0e772a0343fb818df00b74078e91c008632284137d"}, + {file = "pycryptodomex-3.16.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e2453162f473c1eae4826eb10cd7bce19b5facac86d17fb5f29a570fde145abd"}, + {file = "pycryptodomex-3.16.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:0ba28aa97cdd3ff5ed1a4f2b7f5cd04e721166bd75bd2b929e2734433882b583"}, + {file = "pycryptodomex-3.16.0.tar.gz", hash = "sha256:e9ba9d8ed638733c9e95664470b71d624a6def149e2db6cc52c1aca5a6a2df1d"}, +] +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +typed-ast = [ + {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, + {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, + {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, + {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, + {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, + {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, + {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, + {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, + {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, +] +typing-extensions = [ + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, +] +websocket-client = [ + {file = "websocket-client-1.3.3.tar.gz", hash = "sha256:d58c5f284d6a9bf8379dab423259fe8f85b70d5fa5d2916d5791a84594b122b1"}, + {file = "websocket_client-1.3.3-py3-none-any.whl", hash = "sha256:5d55652dc1d0b3c734f044337d929aaf83f4f9138816ec680c1aefefb4dc4877"}, +] +zipp = [ + {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, + {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b05a383 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "python-nostr" +version = "0.1.0" +description = "" +authors = ["Your Name "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.7" +pycryptodomex = "^3.16.0" +websocket-client = "1.3.3" + + +[tool.poetry.group.dev.dependencies] +black = {version = "^22.12.0", allow-prereleases = true} + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" From 269032dd8de836aae6193c5886a8e99e530a9c48 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 26 Dec 2022 15:27:34 +0100 Subject: [PATCH 02/40] clean --- main.py | 65 ++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/main.py b/main.py index 8da93c2..4a17436 100644 --- a/main.py +++ b/main.py @@ -1,57 +1,80 @@ - from nostr.client.client import NostrClient from nostr.event import Event from nostr.key import PublicKey -import asyncio +import asyncio +import threading + async def dm(): print("This is an example NIP-04 DM flow") pk = input("Enter your privatekey to post from (enter nothing for a random one): ") - def callback(event:Event, decrypted_content): - print(f"From {event.public_key[:3]}..{event.public_key[-3:]}: {decrypted_content}") + def callback(event: Event, decrypted_content): + """ + Callback to trigger when a DM is received. + """ + print( + f"From {event.public_key[:3]}..{event.public_key[-3:]}: {decrypted_content}" + ) - client = NostrClient( - privatekey_hex=pk - ) + client = NostrClient(privatekey_hex=pk) await asyncio.sleep(1) - - import threading - t = threading.Thread(target=client.get_dm, args=(client.public_key,callback,)) + t = threading.Thread( + target=client.get_dm, + args=( + client.public_key, + callback, + ), + ) t.start() - to_pubk_hex = input("Enter other pubkey to post to (enter nothing to DM yourself): ") or client.public_key.hex() + to_pubk_hex = ( + input("Enter other pubkey to post to (enter nothing to DM yourself): ") + or client.public_key.hex() + ) print(f"Subscribing to DMs to {to_pubk_hex}") while True: msg = input("\nEnter message: ") client.dm(msg, PublicKey(bytes.fromhex(to_pubk_hex))) + async def post(): print("This posts and reads a nostr note") pk = input("Enter your privatekey to post from (enter nothing for a random one): ") - def callback(event:Event): - print(f"From {event.public_key[:3]}..{event.public_key[-3:]}: {event.content}") + def callback(event: Event): + """ + Callback to trigger when post appers. + """ + print(f"From {event.public_key[:3]}..{event.public_key[-3:]}: {event.content}") - sender_client = NostrClient( - privatekey_hex=pk - ) + sender_client = NostrClient(privatekey_hex=pk) await asyncio.sleep(1) - to_pubk_hex = input("Enter other pubkey (enter nothing to read your own posts): ") or sender_client.public_key.hex() + to_pubk_hex = ( + input("Enter other pubkey (enter nothing to read your own posts): ") + or sender_client.public_key.hex() + ) print(f"Subscribing to posts by {to_pubk_hex}") to_pubk = PublicKey(bytes.fromhex(to_pubk_hex)) - import threading - t = threading.Thread(target=sender_client.get_post, args=(to_pubk, callback,)) + t = threading.Thread( + target=sender_client.get_post, + args=( + to_pubk, + callback, + ), + ) t.start() - while True: msg = input("\nEnter post: ") sender_client.post(msg) -asyncio.run(post()) +# write a DM and receive DMs +asyncio.run(dm()) +# make a post and subscribe to posts +# asyncio.run(post()) From 6c572e1ec0902ac72d5a5cdf117d621b7f1fb1c1 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 26 Dec 2022 15:52:55 +0100 Subject: [PATCH 03/40] relative imports --- __init__.py | 0 nostr/client/client.py | 26 +++++++++++--------------- 2 files changed, 11 insertions(+), 15 deletions(-) create mode 100644 __init__.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nostr/client/client.py b/nostr/client/client.py index 57525cf..d13d918 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -3,17 +3,17 @@ import time import json import os -import base64 +import base64 -from nostr.event import Event -from nostr.relay_manager import RelayManager -from nostr.message_type import ClientMessageType -from nostr.key import PrivateKey, PublicKey +from ..event import Event +from ..relay_manager import RelayManager +from ..message_type import ClientMessageType +from ..key import PrivateKey, PublicKey -from nostr.filter import Filter, Filters -from nostr.event import Event, EventKind -from nostr.relay_manager import RelayManager -from nostr.message_type import ClientMessageType +from ..filter import Filter, Filters +from ..event import Event, EventKind +from ..relay_manager import RelayManager +from ..message_type import ClientMessageType # from aes import AESCipher from . import cbc @@ -84,9 +84,7 @@ def get_post(self, sender_publickey: PublicKey, callback_func=None): def dm(self, message: str, to_pubkey: PublicKey): - shared_secret = self.private_key.compute_shared_secret( - to_pubkey.hex() - ) + shared_secret = self.private_key.compute_shared_secret(to_pubkey.hex()) # print("shared secret: ", shared_secret.hex()) # print("plain text:", message) @@ -95,7 +93,6 @@ def dm(self, message: str, to_pubkey: PublicKey): # print("encrypt iv: ", iv) content = f"{base64.b64encode(enc_text).decode('utf-8')}?iv={base64.b64encode(iv).decode('utf-8')}" - event = Event( self.public_key.hex(), content, @@ -151,7 +148,7 @@ def get_dm(self, sender_publickey: PublicKey, callback_func=None): except: pass # else: - # print(f"\nFrom {event_msg.event.public_key[:5]}...: {event_msg.event.content}") + # print(f"\nFrom {event_msg.event.public_key[:5]}...: {event_msg.event.content}") break time.sleep(0.1) @@ -162,4 +159,3 @@ async def subscribe(self): print(event_msg.event.content) break time.sleep(0.1) - From dfca8bfdc59732fca4c04d3ce762dedb00b2598f Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 26 Dec 2022 16:06:50 +0100 Subject: [PATCH 04/40] two relays --- nostr/client/client.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nostr/client/client.py b/nostr/client/client.py index d13d918..979df28 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -21,9 +21,8 @@ class NostrClient: relays = [ - # "wss://nostr-pub.wellorder.net" + "wss://nostr-pub.wellorder.net", "wss://nostr.zebedee.cloud", - # "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() From 13c8a8d1d55daf88efb64e84c82db8961c0c9a97 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 26 Dec 2022 16:52:48 +0100 Subject: [PATCH 05/40] print hex as default --- nostr/client/client.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nostr/client/client.py b/nostr/client/client.py index 979df28..6c598fe 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -48,8 +48,10 @@ 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 - print(f"Private key: {self.private_key.bech32()} ({self.private_key.hex()})") - print(f"Public key: {self.public_key.bech32()} ({self.public_key.hex()})") + print( + f"Nostr private key: {self.private_key.hex()} ({self.private_key.bech32()})" + ) + print(f"Nostr public key: {self.public_key.hex()} ({self.public_key.bech32()})") def post(self, message: str): event = Event(self.public_key.hex(), message, kind=EventKind.TEXT_NOTE) From 3c27539dd0a83b8e6449a5bb6a1508061c6c246d Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 26 Dec 2022 17:09:27 +0100 Subject: [PATCH 06/40] silent --- nostr/client/client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nostr/client/client.py b/nostr/client/client.py index 6c598fe..f95e6a4 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -48,10 +48,10 @@ 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 - print( - f"Nostr private key: {self.private_key.hex()} ({self.private_key.bech32()})" - ) - print(f"Nostr public key: {self.public_key.hex()} ({self.public_key.bech32()})") + # print( + # f"Nostr private key: {self.private_key.hex()} ({self.private_key.bech32()})" + # ) + # print(f"Nostr public key: {self.public_key.hex()} ({self.public_key.bech32()})") def post(self, message: str): event = Event(self.public_key.hex(), message, kind=EventKind.TEXT_NOTE) From d7fb45f6a1c685be0037afd4e7aa172e0c7e3517 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 26 Dec 2022 17:14:55 +0100 Subject: [PATCH 07/40] connect optional --- nostr/client/client.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/nostr/client/client.py b/nostr/client/client.py index f95e6a4..11119ed 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -29,17 +29,18 @@ class NostrClient: private_key: PrivateKey public_key: PublicKey - def __init__(self, privatekey_hex: str = "", relays: List[str] = []): + def __init__(self, privatekey_hex: str = "", relays: List[str] = [], connect=True): self.generate_keys(privatekey_hex) if len(relays): self.relays = relays - for relay in self.relays: - self.relay_manager.add_relay(relay) - self.relay_manager.open_connections( - {"cert_reqs": ssl.CERT_NONE} - ) # NOTE: This disables ssl certificate verification + if connect: + for relay in self.relays: + self.relay_manager.add_relay(relay) + self.relay_manager.open_connections( + {"cert_reqs": ssl.CERT_NONE} + ) # NOTE: This disables ssl certificate verification def close(self): self.relay_manager.close_connections() From 06362a47a99b79dcb024e01303e773837f1cfd73 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 25 Jan 2023 00:35:48 +0100 Subject: [PATCH 08/40] error checking and reconnect --- nostr/relay.py | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/nostr/relay.py b/nostr/relay.py index ad01ff6..44c05c6 100644 --- a/nostr/relay.py +++ b/nostr/relay.py @@ -1,4 +1,5 @@ import json +import time from threading import Lock from websocket import WebSocketApp from .event import Event @@ -29,6 +30,11 @@ def __init__( self.policy = policy self.message_pool = message_pool self.subscriptions = subscriptions + self.connected: bool = False + self.reconnect: bool = True + self.error_counter: int = 0 + self.error_threshold: int = 0 + self.ssl_options: dict = {} self.lock = Lock() self.ws = WebSocketApp( url, @@ -38,14 +44,26 @@ def __init__( on_close=self._on_close, ) - def connect(self, ssl_options: dict = None): - self.ws.run_forever(sslopt=ssl_options) + def connect(self, ssl_options: dict = {}): + self.ssl_options = ssl_options + self.ws.run_forever(sslopt=self.ssl_options) def close(self): self.ws.close() + def check_reconnect(self): + try: + self.close() + except: + pass + self.connected = False + if self.reconnect: + time.sleep(1) + self.connect(self.ssl_options) + def publish(self, message: str): - self.ws.send(message) + if self.connected: + self.ws.send(message) def add_subscription(self, id, filters: Filters): with self.lock: @@ -71,9 +89,12 @@ def to_json_object(self) -> dict: } def _on_open(self, class_obj): + self.connected = True pass def _on_close(self, class_obj, status_code, message): + self.connected = False + self.check_reconnect() pass def _on_message(self, class_obj, message: str): @@ -81,7 +102,12 @@ def _on_message(self, class_obj, message: str): self.message_pool.add_message(message, self.url) def _on_error(self, class_obj, error): - pass + self.connected = False + self.error_counter += 1 + if self.error_threshold and self.error_counter > self.error_threshold: + pass + else: + self.check_reconnect() def _is_valid_message(self, message: str) -> bool: message = message.strip("\n") @@ -117,7 +143,7 @@ def _is_valid_message(self, message: str) -> bool: with self.lock: subscription = self.subscriptions[subscription_id] - if not subscription.filters.match(event): + if subscription.filters and not subscription.filters.match(event): return False return True From 479b776fe29c539a001e9f64b300614b9ea27d27 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 25 Jan 2023 00:35:48 +0100 Subject: [PATCH 09/40] error checking and reconnect --- nostr/relay.py | 80 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 21 deletions(-) diff --git a/nostr/relay.py b/nostr/relay.py index 9ccb77b..44c05c6 100644 --- a/nostr/relay.py +++ b/nostr/relay.py @@ -1,4 +1,5 @@ import json +import time from threading import Lock from websocket import WebSocketApp from .event import Event @@ -7,44 +8,62 @@ from .message_type import RelayMessageType from .subscription import Subscription + class RelayPolicy: - def __init__(self, should_read: bool=True, should_write: bool=True) -> None: + def __init__(self, should_read: bool = True, should_write: bool = True) -> None: self.should_read = should_read self.should_write = should_write def to_json_object(self) -> dict[str, bool]: - return { - "read": self.should_read, - "write": self.should_write - } + return {"read": self.should_read, "write": self.should_write} + class Relay: def __init__( - self, - url: str, - policy: RelayPolicy, - message_pool: MessagePool, - subscriptions: dict[str, Subscription]={}) -> None: + self, + url: str, + policy: RelayPolicy, + message_pool: MessagePool, + subscriptions: dict[str, Subscription] = {}, + ) -> None: self.url = url self.policy = policy self.message_pool = message_pool self.subscriptions = subscriptions + self.connected: bool = False + self.reconnect: bool = True + self.error_counter: int = 0 + self.error_threshold: int = 0 + self.ssl_options: dict = {} self.lock = Lock() self.ws = WebSocketApp( url, on_open=self._on_open, on_message=self._on_message, on_error=self._on_error, - on_close=self._on_close) + on_close=self._on_close, + ) - def connect(self, ssl_options: dict=None): - self.ws.run_forever(sslopt=ssl_options) + def connect(self, ssl_options: dict = {}): + self.ssl_options = ssl_options + self.ws.run_forever(sslopt=self.ssl_options) def close(self): self.ws.close() + def check_reconnect(self): + try: + self.close() + except: + pass + self.connected = False + if self.reconnect: + time.sleep(1) + self.connect(self.ssl_options) + def publish(self, message: str): - self.ws.send(message) + if self.connected: + self.ws.send(message) def add_subscription(self, id, filters: Filters): with self.lock: @@ -63,25 +82,36 @@ def to_json_object(self) -> dict: return { "url": self.url, "policy": self.policy.to_json_object(), - "subscriptions": [subscription.to_json_object() for subscription in self.subscriptions.values()] + "subscriptions": [ + subscription.to_json_object() + for subscription in self.subscriptions.values() + ], } def _on_open(self, class_obj): + self.connected = True pass def _on_close(self, class_obj, status_code, message): + self.connected = False + self.check_reconnect() pass def _on_message(self, class_obj, message: str): if self._is_valid_message(message): self.message_pool.add_message(message, self.url) - + def _on_error(self, class_obj, error): - pass + self.connected = False + self.error_counter += 1 + if self.error_threshold and self.error_counter > self.error_threshold: + pass + else: + self.check_reconnect() def _is_valid_message(self, message: str) -> bool: message = message.strip("\n") - if not message or message[0] != '[' or message[-1] != ']': + if not message or message[0] != "[" or message[-1] != "]": return False message_json = json.loads(message) @@ -91,21 +121,29 @@ def _is_valid_message(self, message: str) -> bool: if message_type == RelayMessageType.EVENT: if not len(message_json) == 3: return False - + subscription_id = message_json[1] with self.lock: if subscription_id not in self.subscriptions: return False e = message_json[2] - event = Event(e['pubkey'], e['content'], e['created_at'], e['kind'], e['tags'], e['id'], e['sig']) + event = Event( + e["pubkey"], + e["content"], + e["created_at"], + e["kind"], + e["tags"], + e["id"], + e["sig"], + ) if not event.verify(): return False with self.lock: subscription = self.subscriptions[subscription_id] - if not subscription.filters.match(event): + if subscription.filters and not subscription.filters.match(event): return False return True From 09f4b676743bb267be34c30233d5b9140b273c02 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 25 Jan 2023 01:44:11 +0100 Subject: [PATCH 10/40] nostr close --- nostr/relay.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nostr/relay.py b/nostr/relay.py index 44c05c6..da7ecd6 100644 --- a/nostr/relay.py +++ b/nostr/relay.py @@ -94,7 +94,6 @@ def _on_open(self, class_obj): def _on_close(self, class_obj, status_code, message): self.connected = False - self.check_reconnect() pass def _on_message(self, class_obj, message: str): From 3794ef2cc5bdf2e9bbd60fcebdd3d7be81db5ae4 Mon Sep 17 00:00:00 2001 From: calle <93376500+callebtc@users.noreply.github.com> Date: Wed, 25 Jan 2023 01:45:21 +0100 Subject: [PATCH 11/40] Do not reconnect on close --- nostr/relay.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nostr/relay.py b/nostr/relay.py index 44c05c6..da7ecd6 100644 --- a/nostr/relay.py +++ b/nostr/relay.py @@ -94,7 +94,6 @@ def _on_open(self, class_obj): def _on_close(self, class_obj, status_code, message): self.connected = False - self.check_reconnect() pass def _on_message(self, class_obj, message: str): From 76815b77eed230affc8dbd9c6a90a9663484e864 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 31 Jan 2023 16:21:21 +0100 Subject: [PATCH 12/40] count events --- nostr/relay.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nostr/relay.py b/nostr/relay.py index da7ecd6..12be651 100644 --- a/nostr/relay.py +++ b/nostr/relay.py @@ -34,6 +34,10 @@ def __init__( self.reconnect: bool = True self.error_counter: int = 0 self.error_threshold: int = 0 + self.num_received_events: int = 0 + self.num_sent_events: int = 0 + self.num_subscriptions: int = 0 + self.ping: int = 0 self.ssl_options: dict = {} self.lock = Lock() self.ws = WebSocketApp( @@ -63,6 +67,7 @@ def check_reconnect(self): def publish(self, message: str): if self.connected: + self.num_sent_events += 1 self.ws.send(message) def add_subscription(self, id, filters: Filters): @@ -98,6 +103,7 @@ def _on_close(self, class_obj, status_code, message): def _on_message(self, class_obj, message: str): if self._is_valid_message(message): + self.num_received_events += 1 self.message_pool.add_message(message, self.url) def _on_error(self, class_obj, error): From 1d6ee33aa4669b786bedb086451f690e54f9df58 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 1 Feb 2023 14:37:22 +0100 Subject: [PATCH 13/40] subscribe global --- main.py | 15 ++++++++++----- nostr/client/client.py | 8 +++++--- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/main.py b/main.py index 4a17436..4b6b75f 100644 --- a/main.py +++ b/main.py @@ -53,11 +53,16 @@ def callback(event: Event): await asyncio.sleep(1) to_pubk_hex = ( - input("Enter other pubkey (enter nothing to read your own posts): ") + input( + "Enter other pubkey (enter nothing to read your own posts, enter * for all): " + ) or sender_client.public_key.hex() ) - print(f"Subscribing to posts by {to_pubk_hex}") - to_pubk = PublicKey(bytes.fromhex(to_pubk_hex)) + if to_pubk_hex == "*": + to_pubk = None + else: + print(f"Subscribing to posts by {to_pubk_hex}") + to_pubk = PublicKey(bytes.fromhex(to_pubk_hex)) t = threading.Thread( target=sender_client.get_post, @@ -74,7 +79,7 @@ def callback(event: Event): # write a DM and receive DMs -asyncio.run(dm()) +# asyncio.run(dm()) # make a post and subscribe to posts -# asyncio.run(post()) +asyncio.run(post()) diff --git a/nostr/client/client.py b/nostr/client/client.py index 11119ed..e27bc04 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -62,10 +62,12 @@ def post(self, message: str): # print(message) self.relay_manager.publish_message(message) - def get_post(self, sender_publickey: PublicKey, callback_func=None): - filters = Filters( - [Filter(authors=[sender_publickey.hex()], kinds=[EventKind.TEXT_NOTE])] + def get_post(self, sender_publickey: PublicKey = None, callback_func=None): + filter = Filter( + authors=[sender_publickey.hex()] if sender_publickey else None, + kinds=[EventKind.TEXT_NOTE], ) + filters = Filters([filter]) subscription_id = os.urandom(4).hex() self.relay_manager.add_subscription(subscription_id, filters) From 36768d323659c112e8544255a950fea5715c936f Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 1 Feb 2023 15:10:20 +0100 Subject: [PATCH 14/40] add filter and ping --- main.py | 20 ++++++++++++++++++++ nostr/client/client.py | 41 +++++++++++------------------------------ nostr/relay.py | 6 ++++++ 3 files changed, 37 insertions(+), 30 deletions(-) diff --git a/main.py b/main.py index 4b6b75f..56885bb 100644 --- a/main.py +++ b/main.py @@ -3,6 +3,8 @@ from nostr.key import PublicKey import asyncio import threading +import time +import datetime async def dm(): @@ -20,11 +22,20 @@ def callback(event: Event, decrypted_content): client = NostrClient(privatekey_hex=pk) await asyncio.sleep(1) + filters = { + "since": int( + time.mktime( + (datetime.datetime.now() - datetime.timedelta(hours=1)).timetuple() + ) + ) + } + t = threading.Thread( target=client.get_dm, args=( client.public_key, callback, + filters, ), ) t.start() @@ -64,11 +75,20 @@ def callback(event: Event): print(f"Subscribing to posts by {to_pubk_hex}") to_pubk = PublicKey(bytes.fromhex(to_pubk_hex)) + filters = { + "since": int( + time.mktime( + (datetime.datetime.now() - datetime.timedelta(hours=1)).timetuple() + ) + ) + } + t = threading.Thread( target=sender_client.get_post, args=( to_pubk, callback, + filters, ), ) t.start() diff --git a/nostr/client/client.py b/nostr/client/client.py index e27bc04..874c97c 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -23,6 +23,7 @@ class NostrClient: relays = [ "wss://nostr-pub.wellorder.net", "wss://nostr.zebedee.cloud", + "nostr.mom", # "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() @@ -49,23 +50,20 @@ 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 - # print( - # f"Nostr private key: {self.private_key.hex()} ({self.private_key.bech32()})" - # ) - # print(f"Nostr public key: {self.public_key.hex()} ({self.public_key.bech32()})") def post(self, message: str): event = Event(self.public_key.hex(), message, kind=EventKind.TEXT_NOTE) event.sign(self.private_key.hex()) message = json.dumps([ClientMessageType.EVENT, event.to_json_object()]) - # print("Publishing message:") - # print(message) self.relay_manager.publish_message(message) - def get_post(self, sender_publickey: PublicKey = None, callback_func=None): + def get_post( + self, sender_publickey: PublicKey = None, callback_func=None, filter_kwargs={} + ): filter = Filter( authors=[sender_publickey.hex()] if sender_publickey else None, kinds=[EventKind.TEXT_NOTE], + **filter_kwargs, ) filters = Filters([filter]) subscription_id = os.urandom(4).hex() @@ -87,14 +85,9 @@ def get_post(self, sender_publickey: PublicKey = None, callback_func=None): time.sleep(0.1) def dm(self, message: str, to_pubkey: PublicKey): - shared_secret = self.private_key.compute_shared_secret(to_pubkey.hex()) - - # print("shared secret: ", shared_secret.hex()) - # print("plain text:", message) aes = cbc.AESCipher(key=shared_secret) iv, enc_text = aes.encrypt(message) - # print("encrypt iv: ", iv) content = f"{base64.b64encode(enc_text).decode('utf-8')}?iv={base64.b64encode(iv).decode('utf-8')}" event = Event( @@ -105,29 +98,23 @@ def dm(self, message: str, to_pubkey: PublicKey): ) event.sign(self.private_key.hex()) event_message = json.dumps([ClientMessageType.EVENT, event.to_json_object()]) - # print("DM message:") - # print(event_message) time.sleep(1) self.relay_manager.publish_message(event_message) - def get_dm(self, sender_publickey: PublicKey, callback_func=None): - filters = Filters( - [ - Filter( - kinds=[EventKind.ENCRYPTED_DIRECT_MESSAGE], - tags={"#p": [sender_publickey.hex()]}, - ) - ] + def get_dm(self, sender_publickey: PublicKey, callback_func=None, filter_kwargs={}): + filter = Filter( + kinds=[EventKind.ENCRYPTED_DIRECT_MESSAGE], + tags={"#p": [sender_publickey.hex()]}, + **filter_kwargs, ) + filters = Filters([filter]) subscription_id = os.urandom(4).hex() self.relay_manager.add_subscription(subscription_id, filters) request = [ClientMessageType.REQUEST, subscription_id] request.extend(filters.to_json_array()) message = json.dumps(request) - # print("Subscribing to events:") - # print(message) self.relay_manager.publish_message(message) while True: @@ -138,21 +125,15 @@ def get_dm(self, sender_publickey: PublicKey, callback_func=None): shared_secret = self.private_key.compute_shared_secret( event_msg.event.public_key ) - # print("shared secret: ", shared_secret.hex()) - # print("plain text:", message) aes = cbc.AESCipher(key=shared_secret) enc_text_b64, iv_b64 = event_msg.event.content.split("?iv=") iv = base64.decodebytes(iv_b64.encode("utf-8")) enc_text = base64.decodebytes(enc_text_b64.encode("utf-8")) - # print("decrypt iv: ", iv) dec_text = aes.decrypt(iv, enc_text) - # print(f"From {event_msg.event.public_key[:5]}...: {dec_text}") if callback_func: callback_func(event_msg.event, dec_text) except: pass - # else: - # print(f"\nFrom {event_msg.event.public_key[:5]}...: {event_msg.event.content}") break time.sleep(0.1) diff --git a/nostr/relay.py b/nostr/relay.py index 12be651..8265ac6 100644 --- a/nostr/relay.py +++ b/nostr/relay.py @@ -65,6 +65,12 @@ def check_reconnect(self): time.sleep(1) self.connect(self.ssl_options) + def get_pint(self): + if self.connected: + return int(self.ws.last_ping_tm - self.ws.last_pong_tm) + else: + return 0 + def publish(self, message: str): if self.connected: self.num_sent_events += 1 From c22a3e0c435f83f16b4dd5699402c788eb31ab5e Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 1 Feb 2023 15:12:17 +0100 Subject: [PATCH 15/40] rename to ping --- nostr/relay.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nostr/relay.py b/nostr/relay.py index 8265ac6..10d1da6 100644 --- a/nostr/relay.py +++ b/nostr/relay.py @@ -37,7 +37,6 @@ def __init__( self.num_received_events: int = 0 self.num_sent_events: int = 0 self.num_subscriptions: int = 0 - self.ping: int = 0 self.ssl_options: dict = {} self.lock = Lock() self.ws = WebSocketApp( @@ -65,7 +64,7 @@ def check_reconnect(self): time.sleep(1) self.connect(self.ssl_options) - def get_pint(self): + def ping(self): if self.connected: return int(self.ws.last_ping_tm - self.ws.last_pong_tm) else: From 0c9dcc7070dc69979d77679e9383c2aa1df09f02 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 1 Feb 2023 15:14:47 +0100 Subject: [PATCH 16/40] enable ping --- nostr/relay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nostr/relay.py b/nostr/relay.py index 10d1da6..7639c1c 100644 --- a/nostr/relay.py +++ b/nostr/relay.py @@ -49,7 +49,7 @@ def __init__( def connect(self, ssl_options: dict = {}): self.ssl_options = ssl_options - self.ws.run_forever(sslopt=self.ssl_options) + self.ws.run_forever(sslopt=self.ssl_options, ping_interval=2) def close(self): self.ws.close() From f9e1a536099021e8d68a2dc9e5c8701fe33bb2b0 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 1 Feb 2023 15:21:22 +0100 Subject: [PATCH 17/40] ping is a property --- nostr/relay.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nostr/relay.py b/nostr/relay.py index 7639c1c..0e28d49 100644 --- a/nostr/relay.py +++ b/nostr/relay.py @@ -64,6 +64,7 @@ def check_reconnect(self): time.sleep(1) self.connect(self.ssl_options) + @property def ping(self): if self.connected: return int(self.ws.last_ping_tm - self.ws.last_pong_tm) From fa802d78628f1b8696b56f109218fa5b90394aa9 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 1 Feb 2023 15:48:23 +0100 Subject: [PATCH 18/40] verbose stuff --- main.py | 6 ++---- nostr/client/client.py | 8 +++----- nostr/relay.py | 17 ++++++++++++++++- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/main.py b/main.py index 56885bb..050d973 100644 --- a/main.py +++ b/main.py @@ -20,7 +20,6 @@ def callback(event: Event, decrypted_content): ) client = NostrClient(privatekey_hex=pk) - await asyncio.sleep(1) filters = { "since": int( @@ -61,7 +60,6 @@ def callback(event: Event): print(f"From {event.public_key[:3]}..{event.public_key[-3:]}: {event.content}") sender_client = NostrClient(privatekey_hex=pk) - await asyncio.sleep(1) to_pubk_hex = ( input( @@ -99,7 +97,7 @@ def callback(event: Event): # write a DM and receive DMs -# asyncio.run(dm()) +asyncio.run(dm()) # make a post and subscribe to posts -asyncio.run(post()) +# asyncio.run(post()) diff --git a/nostr/client/client.py b/nostr/client/client.py index 874c97c..a8836f7 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -21,9 +21,11 @@ class NostrClient: relays = [ + "wss://relay.snort.social", "wss://nostr-pub.wellorder.net", "wss://nostr.zebedee.cloud", - "nostr.mom", + "wss://nostr.mom", + # "wss://wss://lnbits.link/nostrrelay/client" # "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() @@ -72,8 +74,6 @@ def get_post( request = [ClientMessageType.REQUEST, subscription_id] request.extend(filters.to_json_array()) message = json.dumps(request) - # print("Subscribing to events:") - # print(message) self.relay_manager.publish_message(message) while True: @@ -98,8 +98,6 @@ def dm(self, message: str, to_pubkey: PublicKey): ) event.sign(self.private_key.hex()) event_message = json.dumps([ClientMessageType.EVENT, event.to_json_object()]) - - time.sleep(1) self.relay_manager.publish_message(event_message) def get_dm(self, sender_publickey: PublicKey, callback_func=None, filter_kwargs={}): diff --git a/nostr/relay.py b/nostr/relay.py index 0e28d49..98012b3 100644 --- a/nostr/relay.py +++ b/nostr/relay.py @@ -45,13 +45,17 @@ def __init__( 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 = {}): self.ssl_options = ssl_options - self.ws.run_forever(sslopt=self.ssl_options, ping_interval=2) + print(self.url, "🟢") + self.ws.run_forever(sslopt=self.ssl_options) def close(self): + print(self.url, "🔴") self.ws.close() def check_reconnect(self): @@ -111,8 +115,11 @@ def _on_message(self, class_obj, message: str): if self._is_valid_message(message): self.num_received_events += 1 self.message_pool.add_message(message, self.url) + else: + print(self.url, "invalid message", message) def _on_error(self, class_obj, error): + print(self.url, "🚫", error) self.connected = False self.error_counter += 1 if self.error_threshold and self.error_counter > self.error_threshold: @@ -120,6 +127,14 @@ def _on_error(self, class_obj, error): else: self.check_reconnect() + def _on_ping(self, class_obj, message): + print(self.url, "ping", message) + return + + def _on_pong(self, class_obj, message): + print(self.url, "pong", message) + return + def _is_valid_message(self, message: str) -> bool: message = message.strip("\n") if not message or message[0] != "[" or message[-1] != "]": From 88d436799e2606f70c4f04d0788608fe51c68919 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 2 Feb 2023 16:35:57 +0100 Subject: [PATCH 19/40] also reuse proxy --- nostr/relay.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/nostr/relay.py b/nostr/relay.py index 9aa9595..25e2bf8 100644 --- a/nostr/relay.py +++ b/nostr/relay.py @@ -35,6 +35,7 @@ def __init__( self.error_counter: int = 0 self.error_threshold: int = 0 self.ssl_options: dict = {} + self.proxy: dict = {} self.lock = Lock() self.ws = WebSocketApp( url, @@ -44,13 +45,14 @@ def __init__( on_close=self._on_close, ) - def connect(self, ssl_options: dict=None, proxy: dict=None): + def connect(self, ssl_options: dict = None, proxy: dict = None): self.ssl_options = ssl_options + self.proxy = proxy self.ws.run_forever( sslopt=ssl_options, - http_proxy_host=None if proxy is None else proxy.get('host'), - http_proxy_port=None if proxy is None else proxy.get('port'), - proxy_type=None if proxy is None else proxy.get('type') + http_proxy_host=None if proxy is None else proxy.get("host"), + http_proxy_port=None if proxy is None else proxy.get("port"), + proxy_type=None if proxy is None else proxy.get("type"), ) def close(self): @@ -64,7 +66,7 @@ def check_reconnect(self): self.connected = False if self.reconnect: time.sleep(1) - self.connect(self.ssl_options) + self.connect(self.ssl_options, self.proxy) def publish(self, message: str): if self.connected: From 4a56c00ba014b4cdbc9a668f8ee4a0c40f98924f Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 2 Feb 2023 17:05:22 +0100 Subject: [PATCH 20/40] update client to main --- main.py | 4 +- nostr/client/client.py | 10 +- nostr/filter.py | 76 ++++-------- poetry.lock | 262 +---------------------------------------- pyproject.toml | 6 + 5 files changed, 39 insertions(+), 319 deletions(-) diff --git a/main.py b/main.py index 4a17436..ddeefbd 100644 --- a/main.py +++ b/main.py @@ -74,7 +74,7 @@ def callback(event: Event): # write a DM and receive DMs -asyncio.run(dm()) +# asyncio.run(dm()) # make a post and subscribe to posts -# asyncio.run(post()) +asyncio.run(post()) diff --git a/nostr/client/client.py b/nostr/client/client.py index 11119ed..db924a4 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -56,8 +56,8 @@ def generate_keys(self, privatekey_hex: str = None): def post(self, message: str): event = Event(self.public_key.hex(), message, kind=EventKind.TEXT_NOTE) - event.sign(self.private_key.hex()) - message = json.dumps([ClientMessageType.EVENT, event.to_json_object()]) + event.signature = self.private_key.sign_event(event) + message = json.dumps([ClientMessageType.EVENT, event.to_message()]) # print("Publishing message:") # print(message) self.relay_manager.publish_message(message) @@ -101,8 +101,8 @@ def dm(self, message: str, to_pubkey: PublicKey): tags=[["p", to_pubkey.hex()]], kind=EventKind.ENCRYPTED_DIRECT_MESSAGE, ) - event.sign(self.private_key.hex()) - event_message = json.dumps([ClientMessageType.EVENT, event.to_json_object()]) + event.signature = self.private_key.sign_event(event) + event_message = json.dumps([ClientMessageType.EVENT, event.to_message()]) # print("DM message:") # print(event_message) @@ -114,7 +114,7 @@ def get_dm(self, sender_publickey: PublicKey, callback_func=None): [ Filter( kinds=[EventKind.ENCRYPTED_DIRECT_MESSAGE], - tags={"#p": [sender_publickey.hex()]}, + pubkey_refs={"#p": [sender_publickey.hex()]}, ) ] ) diff --git a/nostr/filter.py b/nostr/filter.py index bd62af7..f119079 100644 --- a/nostr/filter.py +++ b/nostr/filter.py @@ -4,8 +4,6 @@ from .event import Event, EventKind - - class Filter: """ NIP-01 filtering. @@ -17,34 +15,27 @@ class Filter: added. For example: # arbitrary tag filter.add_arbitrary_tag('t', [hashtags]) - + # promoted to explicit support Filter(hashtag_refs=[hashtags]) """ + def __init__( -<<<<<<< HEAD self, - ids: "list[str]" = None, - kinds: "list[int]" = None, - authors: "list[str]" = None, + event_ids: List[str] = None, + kinds: List[EventKind] = None, + authors: List[str] = None, since: int = None, until: int = None, - tags: "dict[str, list[str]]" = None, + event_refs: List[ + str + ] = None, # the "#e" attr; list of event ids referenced in an "e" tag + pubkey_refs: List[ + str + ] = None, # The "#p" attr; list of pubkeys referenced in a "p" tag limit: int = None, ) -> None: - self.IDs = ids -======= - self, - event_ids: List[str] = None, - kinds: List[EventKind] = None, - authors: List[str] = None, - since: int = None, - until: int = None, - event_refs: List[str] = None, # the "#e" attr; list of event ids referenced in an "e" tag - pubkey_refs: List[str] = None, # The "#p" attr; list of pubkeys referenced in a "p" tag - limit: int = None) -> None: self.event_ids = event_ids ->>>>>>> bda320f6d6d5087fe1afecd122831afe025f7633 self.kinds = kinds self.authors = authors self.since = since @@ -55,21 +46,19 @@ def __init__( self.tags = {} if self.event_refs: - self.add_arbitrary_tag('e', self.event_refs) + self.add_arbitrary_tag("e", self.event_refs) if self.pubkey_refs: - self.add_arbitrary_tag('p', self.pubkey_refs) - + self.add_arbitrary_tag("p", self.pubkey_refs) def add_arbitrary_tag(self, tag: str, values: list): """ - Filter on any arbitrary tag with explicit handling for NIP-01 and NIP-12 - single-letter tags. + Filter on any arbitrary tag with explicit handling for NIP-01 and NIP-12 + single-letter tags. """ - # NIP-01 'e' and 'p' tags and any NIP-12 single-letter tags must be prefixed with "#" + # NIP-01 'e' and 'p' tags and any NIP-12 single-letter tags must be prefixed with "#" tag_key = tag if len(tag) > 1 else f"#{tag}" self.tags[tag_key] = values - def matches(self, event: Event) -> bool: if self.event_ids is not None and event.id not in self.event_ids: return False @@ -81,16 +70,13 @@ def matches(self, event: Event) -> bool: return False if self.until is not None and event.created_at > self.until: return False - if (self.event_refs is not None or self.pubkey_refs is not None) and len(event.tags) == 0: + if (self.event_refs is not None or self.pubkey_refs is not None) and len( + event.tags + ) == 0: return False -<<<<<<< HEAD - if self.tags != None: - e_tag_identifiers = [e_tag[0] for e_tag in event.tags] -======= if self.tags: e_tag_identifiers = set([e_tag[0] for e_tag in event.tags]) ->>>>>>> bda320f6d6d5087fe1afecd122831afe025f7633 for f_tag, f_tag_values in self.tags.items(): # Omit any NIP-01 or NIP-12 "#" chars on single-letter tags f_tag = f_tag.replace("#", "") @@ -98,38 +84,26 @@ def matches(self, event: Event) -> bool: if f_tag not in e_tag_identifiers: # Event is missing a tag type that we're looking for return False - + # Multiple values within f_tag_values are treated as OR search; an Event # needs to match only one. # Note: an Event could have multiple entries of the same tag type # (e.g. a reply to multiple people) so we have to check all of them. match_found = False for e_tag in event.tags: -<<<<<<< HEAD - if e_tag[1] not in f_tag_values: - return False -======= if e_tag[0] == f_tag and e_tag[1] in f_tag_values: match_found = True break if not match_found: return False ->>>>>>> bda320f6d6d5087fe1afecd122831afe025f7633 return True - def to_json_object(self) -> dict: res = {} -<<<<<<< HEAD - if self.IDs != None: - res["ids"] = self.IDs - if self.kinds != None: -======= if self.event_ids is not None: res["ids"] = self.event_ids - if self.kinds is not None: ->>>>>>> bda320f6d6d5087fe1afecd122831afe025f7633 + if self.kinds is not None: res["kinds"] = self.kinds if self.authors is not None: res["authors"] = self.authors @@ -145,10 +119,6 @@ def to_json_object(self) -> dict: return res -<<<<<<< HEAD -======= - ->>>>>>> bda320f6d6d5087fe1afecd122831afe025f7633 class Filters(UserList): def __init__(self, initlist: "list[Filter]" = []) -> None: super().__init__(initlist) @@ -161,8 +131,4 @@ def match(self, event: Event): return False def to_json_array(self) -> list: -<<<<<<< HEAD - return [filter.to_json_object() for filter in self.data] -======= return [filter.to_json_object() for filter in self.data] ->>>>>>> bda320f6d6d5087fe1afecd122831afe025f7633 diff --git a/poetry.lock b/poetry.lock index 7bcba2b..4e433ab 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,259 +1,7 @@ -[[package]] -name = "black" -version = "22.12.0" -description = "The uncompromising code formatter." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} -typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "click" -version = "8.1.3" -description = "Composable command line interface toolkit" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" - -[[package]] -name = "importlib-metadata" -version = "5.2.0" -description = "Read metadata from Python packages" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] - -[[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "pathspec" -version = "0.10.3" -description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "platformdirs" -version = "2.6.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] -test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] - -[[package]] -name = "pycryptodomex" -version = "3.16.0" -description = "Cryptographic library for Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "typed-ast" -version = "1.5.4" -description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "typing-extensions" -version = "4.4.0" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "websocket-client" -version = "1.3.3" -description = "WebSocket client for Python with low level API options" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"] -optional = ["python-socks", "wsaccel"] -test = ["websockets"] - -[[package]] -name = "zipp" -version = "3.11.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +# This file is automatically @generated by Poetry and should not be changed by hand. +package = [] [metadata] -lock-version = "1.1" -python-versions = "^3.7" -content-hash = "b4819f5f4403092e6aafe15d38b9a0850eff2c7b5f9417b2cd0ad5b91c913bcd" - -[metadata.files] -black = [ - {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, - {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, - {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, - {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, - {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, - {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, - {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, - {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, - {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, - {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, - {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, - {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, -] -click = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, -] -colorama = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -importlib-metadata = [ - {file = "importlib_metadata-5.2.0-py3-none-any.whl", hash = "sha256:0eafa39ba42bf225fc00e67f701d71f85aead9f878569caf13c3724f704b970f"}, - {file = "importlib_metadata-5.2.0.tar.gz", hash = "sha256:404d48d62bba0b7a77ff9d405efd91501bef2e67ff4ace0bed40a0cf28c3c7cd"}, -] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] -pathspec = [ - {file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"}, - {file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"}, -] -platformdirs = [ - {file = "platformdirs-2.6.0-py3-none-any.whl", hash = "sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca"}, - {file = "platformdirs-2.6.0.tar.gz", hash = "sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e"}, -] -pycryptodomex = [ - {file = "pycryptodomex-3.16.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b3d04c00d777c36972b539fb79958790126847d84ec0129fce1efef250bfe3ce"}, - {file = "pycryptodomex-3.16.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e5a670919076b71522c7d567a9043f66f14b202414a63c3a078b5831ae342c03"}, - {file = "pycryptodomex-3.16.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:ce338a9703f54b2305a408fc9890eb966b727ce72b69f225898bb4e9d9ed3f1f"}, - {file = "pycryptodomex-3.16.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:a1c0ae7123448ecb034c75c713189cb00ebe2d415b11682865b6c54d200d9c93"}, - {file = "pycryptodomex-3.16.0-cp27-cp27m-win32.whl", hash = "sha256:8851585ff19871e5d69e1790f4ca5f6fd1699d6b8b14413b472a4c0dbc7ea780"}, - {file = "pycryptodomex-3.16.0-cp27-cp27m-win_amd64.whl", hash = "sha256:8dd2d9e3c617d0712ed781a77efd84ea579e76c5f9b2a4bc0b684ebeddf868b2"}, - {file = "pycryptodomex-3.16.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:2ad9bb86b355b6104796567dd44c215b3dc953ef2fae5e0bdfb8516731df92cf"}, - {file = "pycryptodomex-3.16.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:e25a2f5667d91795f9417cb856f6df724ccdb0cdd5cbadb212ee9bf43946e9f8"}, - {file = "pycryptodomex-3.16.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:b0789a8490114a2936ed77c87792cfe77582c829cb43a6d86ede0f9624ba8aa3"}, - {file = "pycryptodomex-3.16.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0da835af786fdd1c9930994c78b23e88d816dc3f99aa977284a21bbc26d19735"}, - {file = "pycryptodomex-3.16.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:22aed0868622d95179217c298e37ed7410025c7b29dac236d3230617d1e4ed56"}, - {file = "pycryptodomex-3.16.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1619087fb5b31510b0b0b058a54f001a5ffd91e6ffee220d9913064519c6a69d"}, - {file = "pycryptodomex-3.16.0-cp35-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:70288d9bfe16b2fd0d20b6c365db614428f1bcde7b20d56e74cf88ade905d9eb"}, - {file = "pycryptodomex-3.16.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7993d26dae4d83b8f4ce605bb0aecb8bee330bb3c95475ef06f3694403621e71"}, - {file = "pycryptodomex-3.16.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:1cda60207be8c1cf0b84b9138f9e3ca29335013d2b690774a5e94678ff29659a"}, - {file = "pycryptodomex-3.16.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:04610536921c1ec7adba158ef570348550c9f3a40bc24be9f8da2ef7ab387981"}, - {file = "pycryptodomex-3.16.0-cp35-abi3-win32.whl", hash = "sha256:daa67f5ebb6fbf1ee9c90decaa06ca7fc88a548864e5e484d52b0920a57fe8a5"}, - {file = "pycryptodomex-3.16.0-cp35-abi3-win_amd64.whl", hash = "sha256:231dc8008cbdd1ae0e34645d4523da2dbc7a88c325f0d4a59635a86ee25b41dd"}, - {file = "pycryptodomex-3.16.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:4dbbe18cc232b5980c7633972ae5417d0df76fe89e7db246eefd17ef4d8e6d7a"}, - {file = "pycryptodomex-3.16.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:893f8a97d533c66cc3a56e60dd3ed40a3494ddb4aafa7e026429a08772f8a849"}, - {file = "pycryptodomex-3.16.0-pp27-pypy_73-win32.whl", hash = "sha256:6a465e4f856d2a4f2a311807030c89166529ccf7ccc65bef398de045d49144b6"}, - {file = "pycryptodomex-3.16.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba57ac7861fd2c837cdb33daf822f2a052ff57dd769a2107807f52a36d0e8d38"}, - {file = "pycryptodomex-3.16.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f2b971a7b877348a27dcfd0e772a0343fb818df00b74078e91c008632284137d"}, - {file = "pycryptodomex-3.16.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e2453162f473c1eae4826eb10cd7bce19b5facac86d17fb5f29a570fde145abd"}, - {file = "pycryptodomex-3.16.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:0ba28aa97cdd3ff5ed1a4f2b7f5cd04e721166bd75bd2b929e2734433882b583"}, - {file = "pycryptodomex-3.16.0.tar.gz", hash = "sha256:e9ba9d8ed638733c9e95664470b71d624a6def149e2db6cc52c1aca5a6a2df1d"}, -] -tomli = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] -typed-ast = [ - {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, - {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, - {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, - {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, - {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, - {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, - {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, - {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, - {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, - {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, -] -typing-extensions = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, -] -websocket-client = [ - {file = "websocket-client-1.3.3.tar.gz", hash = "sha256:d58c5f284d6a9bf8379dab423259fe8f85b70d5fa5d2916d5791a84594b122b1"}, - {file = "websocket_client-1.3.3-py3-none-any.whl", hash = "sha256:5d55652dc1d0b3c734f044337d929aaf83f4f9138816ec680c1aefefb4dc4877"}, -] -zipp = [ - {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, - {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, -] +lock-version = "2.0" +python-versions = "*" +content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" diff --git a/pyproject.toml b/pyproject.toml index 88b823f..9d733ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,9 @@ +[tool.poetry] +name = "python-nostr" +version = "0.1.0" +description = "" +authors = ["Your Name "] + [build-system] requires = ["setuptools", "setuptools-scm"] build-backend = "setuptools.build_meta" From 15b16dab98c12f0a97d8a1390b11915080d93f03 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 7 Feb 2023 15:58:39 +0100 Subject: [PATCH 21/40] not working client --- main.py | 4 ++-- nostr/client/client.py | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/main.py b/main.py index ddeefbd..4a17436 100644 --- a/main.py +++ b/main.py @@ -74,7 +74,7 @@ def callback(event: Event): # write a DM and receive DMs -# asyncio.run(dm()) +asyncio.run(dm()) # make a post and subscribe to posts -asyncio.run(post()) +# asyncio.run(post()) diff --git a/nostr/client/client.py b/nostr/client/client.py index db924a4..c931b87 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -21,8 +21,9 @@ class NostrClient: relays = [ - "wss://nostr-pub.wellorder.net", - "wss://nostr.zebedee.cloud", + "wss://lnbits.link/nostrrelay/client" + # "wss://nostr-pub.wellorder.net", + # "wss://nostr.zebedee.cloud", # "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() @@ -56,7 +57,7 @@ def generate_keys(self, privatekey_hex: str = None): def post(self, message: str): event = Event(self.public_key.hex(), message, kind=EventKind.TEXT_NOTE) - event.signature = self.private_key.sign_event(event) + self.private_key.sign_event(event) message = json.dumps([ClientMessageType.EVENT, event.to_message()]) # print("Publishing message:") # print(message) @@ -101,7 +102,7 @@ def dm(self, message: str, to_pubkey: PublicKey): tags=[["p", to_pubkey.hex()]], kind=EventKind.ENCRYPTED_DIRECT_MESSAGE, ) - event.signature = self.private_key.sign_event(event) + self.private_key.sign_event(event) event_message = json.dumps([ClientMessageType.EVENT, event.to_message()]) # print("DM message:") # print(event_message) From 2dfb24ac4ba468a1df47cf8705fddcbb796c5ee5 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 7 Feb 2023 21:57:17 +0100 Subject: [PATCH 22/40] fix: Events initialization --- nostr/message_pool.py | 17 +++++++++++++---- nostr/relay.py | 3 +-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/nostr/message_pool.py b/nostr/message_pool.py index ac46b24..d364cf2 100644 --- a/nostr/message_pool.py +++ b/nostr/message_pool.py @@ -4,22 +4,26 @@ from .message_type import RelayMessageType from .event import Event + class EventMessage: def __init__(self, event: Event, subscription_id: str, url: str) -> None: self.event = event self.subscription_id = subscription_id self.url = url + class NoticeMessage: def __init__(self, content: str, url: str) -> None: self.content = content self.url = url + class EndOfStoredEventsMessage: def __init__(self, subscription_id: str, url: str) -> None: self.subscription_id = subscription_id self.url = url + class MessagePool: def __init__(self) -> None: self.events: Queue[EventMessage] = Queue() @@ -27,7 +31,7 @@ def __init__(self) -> None: self.eose_notices: Queue[EndOfStoredEventsMessage] = Queue() self._unique_events: set = set() self.lock: Lock = Lock() - + def add_message(self, message: str, url: str): self._process_message(message, url) @@ -55,7 +59,14 @@ def _process_message(self, message: str, url: str): if message_type == RelayMessageType.EVENT: subscription_id = message_json[1] e = message_json[2] - event = Event(e['pubkey'], e['content'], e['created_at'], e['kind'], e['tags'], e['id'], e['sig']) + event = Event( + e["content"], + e["pubkey"], + e["created_at"], + e["kind"], + e["tags"], + e["sig"], + ) with self.lock: if not event.id in self._unique_events: self.events.put(EventMessage(event, subscription_id, url)) @@ -64,5 +75,3 @@ def _process_message(self, message: str, url: str): self.notices.put(NoticeMessage(message_json[1], url)) elif message_type == RelayMessageType.END_OF_STORED_EVENTS: self.eose_notices.put(EndOfStoredEventsMessage(message_json[1], url)) - - diff --git a/nostr/relay.py b/nostr/relay.py index 25e2bf8..cc3d017 100644 --- a/nostr/relay.py +++ b/nostr/relay.py @@ -135,12 +135,11 @@ def _is_valid_message(self, message: str) -> bool: e = message_json[2] event = Event( - e["pubkey"], e["content"], + e["pubkey"], e["created_at"], e["kind"], e["tags"], - e["id"], e["sig"], ) if not event.verify(): From c9c00007c74cbe95031a5b8b33fa6dc1cd747680 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 7 Feb 2023 21:57:30 +0100 Subject: [PATCH 23/40] udpate client --- nostr/client/client.py | 44 ++++++++++++------------------------------ nostr/relay_manager.py | 17 ++++++++-------- 2 files changed, 21 insertions(+), 40 deletions(-) diff --git a/nostr/client/client.py b/nostr/client/client.py index c931b87..16cf96a 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -11,7 +11,7 @@ from ..key import PrivateKey, PublicKey from ..filter import Filter, Filters -from ..event import Event, EventKind +from ..event import Event, EventKind, EncryptedDirectMessage from ..relay_manager import RelayManager from ..message_type import ClientMessageType @@ -21,8 +21,8 @@ class NostrClient: relays = [ - "wss://lnbits.link/nostrrelay/client" - # "wss://nostr-pub.wellorder.net", + # "wss://lnbits.link/nostrrelay/client" + "wss://nostr-pub.wellorder.net", # "wss://nostr.zebedee.cloud", # "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" @@ -56,12 +56,12 @@ def generate_keys(self, privatekey_hex: str = None): # print(f"Nostr public key: {self.public_key.hex()} ({self.public_key.bech32()})") def post(self, message: str): - event = Event(self.public_key.hex(), message, kind=EventKind.TEXT_NOTE) + event = Event(message, self.public_key.hex(), kind=EventKind.TEXT_NOTE) self.private_key.sign_event(event) - message = json.dumps([ClientMessageType.EVENT, event.to_message()]) + event_json = event.to_message() # print("Publishing message:") - # print(message) - self.relay_manager.publish_message(message) + # print(event_json) + self.relay_manager.publish_message(event_json) def get_post(self, sender_publickey: PublicKey, callback_func=None): filters = Filters( @@ -80,42 +80,23 @@ def get_post(self, sender_publickey: PublicKey, callback_func=None): 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_func: callback_func(event_msg.event) time.sleep(0.1) def dm(self, message: str, to_pubkey: PublicKey): - - shared_secret = self.private_key.compute_shared_secret(to_pubkey.hex()) - - # print("shared secret: ", shared_secret.hex()) - # print("plain text:", message) - aes = cbc.AESCipher(key=shared_secret) - iv, enc_text = aes.encrypt(message) - # print("encrypt iv: ", iv) - content = f"{base64.b64encode(enc_text).decode('utf-8')}?iv={base64.b64encode(iv).decode('utf-8')}" - - event = Event( - self.public_key.hex(), - content, - tags=[["p", to_pubkey.hex()]], - kind=EventKind.ENCRYPTED_DIRECT_MESSAGE, + dm = EncryptedDirectMessage( + recipient_pubkey=to_pubkey.hex(), cleartext_content=message ) - self.private_key.sign_event(event) - event_message = json.dumps([ClientMessageType.EVENT, event.to_message()]) - # print("DM message:") - # print(event_message) - - time.sleep(1) - self.relay_manager.publish_message(event_message) + self.private_key.sign_event(dm) + self.relay_manager.publish_event(dm) def get_dm(self, sender_publickey: PublicKey, callback_func=None): filters = Filters( [ Filter( kinds=[EventKind.ENCRYPTED_DIRECT_MESSAGE], - pubkey_refs={"#p": [sender_publickey.hex()]}, + pubkey_refs=[sender_publickey.hex()], ) ] ) @@ -159,6 +140,5 @@ async def subscribe(self): while True: while self.relay_manager.message_pool.has_events(): event_msg = self.relay_manager.message_pool.get_event() - print(event_msg.event.content) break time.sleep(0.1) diff --git a/nostr/relay_manager.py b/nostr/relay_manager.py index 191f5bd..c2ac29b 100644 --- a/nostr/relay_manager.py +++ b/nostr/relay_manager.py @@ -8,18 +8,18 @@ from .relay import Relay, RelayPolicy - class RelayException(Exception): pass - class RelayManager: def __init__(self) -> None: self.relays: dict[str, Relay] = {} self.message_pool = MessagePool() - def add_relay(self, url: str, read: bool=True, write: bool=True, subscriptions={}): + def add_relay( + self, url: str, read: bool = True, write: bool = True, subscriptions={} + ): policy = RelayPolicy(read, write) relay = Relay(url, policy, self.message_pool, subscriptions) self.relays[url] = relay @@ -35,12 +35,12 @@ def close_subscription(self, id: str): for relay in self.relays.values(): relay.close_subscription(id) - 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(): threading.Thread( target=relay.connect, args=(ssl_options, proxy), - name=f"{relay.url}-thread" + name=f"{relay.url}-thread", ).start() def close_connections(self): @@ -53,11 +53,12 @@ def publish_message(self, message: str): relay.publish(message) def publish_event(self, event: Event): - """ Verifies that the Event is publishable before submitting it to relays """ + """Verifies that the Event is publishable before submitting it to relays""" if event.signature is None: raise RelayException(f"Could not publish {event.id}: must be signed") if not event.verify(): - raise RelayException(f"Could not publish {event.id}: failed to verify signature {event.signature}") - + raise RelayException( + f"Could not publish {event.id}: failed to verify signature {event.signature}" + ) self.publish_message(event.to_message()) From 8e39666fe22c8c7ed9c116bd30e014090ea89218 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 7 Feb 2023 22:08:50 +0100 Subject: [PATCH 24/40] update client --- main.py | 11 ++++++----- nostr/client/client.py | 14 ++++++++------ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/main.py b/main.py index 4a17436..06bab60 100644 --- a/main.py +++ b/main.py @@ -73,8 +73,9 @@ def callback(event: Event): sender_client.post(msg) -# write a DM and receive DMs -asyncio.run(dm()) - -# make a post and subscribe to posts -# asyncio.run(post()) +if input("Enter 1 for DM, 2 for Posts (Default: 1)") or 1 == 1: + # write a DM and receive DMs + asyncio.run(dm()) +else: + # make a post and subscribe to posts + asyncio.run(post()) diff --git a/nostr/client/client.py b/nostr/client/client.py index 16cf96a..8cceb42 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -35,13 +35,15 @@ def __init__(self, privatekey_hex: str = "", relays: List[str] = [], connect=Tru if len(relays): self.relays = relays - if connect: - for relay in self.relays: - self.relay_manager.add_relay(relay) - self.relay_manager.open_connections( - {"cert_reqs": ssl.CERT_NONE} - ) # NOTE: This disables ssl certificate verification + self.connect() + + def connect(self): + for relay in self.relays: + self.relay_manager.add_relay(relay) + self.relay_manager.open_connections( + {"cert_reqs": ssl.CERT_NONE} + ) # NOTE: This disables ssl certificate verification def close(self): self.relay_manager.close_connections() From 1812b0128f230a7b8be5df48ff7077edc6b64484 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 7 Feb 2023 23:05:32 +0100 Subject: [PATCH 25/40] message queues --- main.py | 4 ++-- nostr/client/client.py | 8 ++++---- nostr/relay.py | 13 +++++++++++-- nostr/relay_manager.py | 7 +++++++ 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/main.py b/main.py index 06bab60..c506783 100644 --- a/main.py +++ b/main.py @@ -18,7 +18,7 @@ def callback(event: Event, decrypted_content): ) client = NostrClient(privatekey_hex=pk) - await asyncio.sleep(1) + # await asyncio.sleep(1) t = threading.Thread( target=client.get_dm, @@ -50,7 +50,7 @@ def callback(event: Event): print(f"From {event.public_key[:3]}..{event.public_key[-3:]}: {event.content}") sender_client = NostrClient(privatekey_hex=pk) - await asyncio.sleep(1) + # await asyncio.sleep(1) to_pubk_hex = ( input("Enter other pubkey (enter nothing to read your own posts): ") diff --git a/nostr/client/client.py b/nostr/client/client.py index 8cceb42..16e3c64 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -21,10 +21,9 @@ class NostrClient: relays = [ - # "wss://lnbits.link/nostrrelay/client" - "wss://nostr-pub.wellorder.net", - # "wss://nostr.zebedee.cloud", - # "wss://no.str.cr", + "wss://lnbits.link/nostrrelay/client" "wss://nostr-pub.wellorder.net", + "wss://nostr.zebedee.cloud", + "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() private_key: PrivateKey @@ -44,6 +43,7 @@ def connect(self): self.relay_manager.open_connections( {"cert_reqs": ssl.CERT_NONE} ) # NOTE: This disables ssl certificate verification + self.relay_manager.start_message_workers() def close(self): self.relay_manager.close_connections() diff --git a/nostr/relay.py b/nostr/relay.py index cc3d017..e47a17e 100644 --- a/nostr/relay.py +++ b/nostr/relay.py @@ -1,5 +1,6 @@ import json import time +from queue import Queue from threading import Lock from websocket import WebSocketApp from .event import Event @@ -37,6 +38,7 @@ def __init__( self.ssl_options: dict = {} self.proxy: dict = {} self.lock = Lock() + self.queue = Queue() self.ws = WebSocketApp( url, on_open=self._on_open, @@ -69,8 +71,15 @@ def check_reconnect(self): self.connect(self.ssl_options, self.proxy) def publish(self, message: str): - if self.connected: - self.ws.send(message) + self.queue.put(message) + + def queue_worker(self): + while True: + if self.connected: + message = self.queue.get() + self.ws.send(message) + else: + time.sleep(0.1) def add_subscription(self, id, filters: Filters): with self.lock: diff --git a/nostr/relay_manager.py b/nostr/relay_manager.py index c2ac29b..44d9e7b 100644 --- a/nostr/relay_manager.py +++ b/nostr/relay_manager.py @@ -43,6 +43,13 @@ def open_connections(self, ssl_options: dict = None, proxy: dict = None): name=f"{relay.url}-thread", ).start() + def start_message_workers(self): + for relay in self.relays.values(): + threading.Thread( + target=relay.queue_worker, + name=f"{relay.url}-queue", + ).start() + def close_connections(self): for relay in self.relays.values(): relay.close() From 5054fea937ebaf03bb007bf1d0b6de8617b66393 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 7 Feb 2023 23:11:43 +0100 Subject: [PATCH 26/40] fix filter --- main.py | 1 - nostr/client/client.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/main.py b/main.py index 2d895d1..b4fbb93 100644 --- a/main.py +++ b/main.py @@ -34,7 +34,6 @@ def callback(event: Event, decrypted_content): args=( client.public_key, callback, - filters, ), ) t.start() diff --git a/nostr/client/client.py b/nostr/client/client.py index 9ae36b5..b7454d8 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -21,7 +21,7 @@ class NostrClient: relays = [ - "wss://relay.snort.social", + # "wss://relay.snort.social", "wss://nostr-pub.wellorder.net", "wss://nostr.zebedee.cloud", "wss://nostr.mom", @@ -103,7 +103,6 @@ def get_dm(self, sender_publickey: PublicKey, callback_func=None): ) ] ) - filters = Filters([filter]) subscription_id = os.urandom(4).hex() self.relay_manager.add_subscription(subscription_id, filters) From c4fbeb83d7fcd53e828f52dde5d8d548a6469829 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 8 Feb 2023 09:55:49 +0100 Subject: [PATCH 27/40] update client --- main.py | 12 +++++++++- nostr/event.py | 65 ++++++++++++++++++++++++++------------------------ nostr/relay.py | 6 ++--- 3 files changed, 47 insertions(+), 36 deletions(-) diff --git a/main.py b/main.py index aee2128..3610848 100644 --- a/main.py +++ b/main.py @@ -7,6 +7,14 @@ import datetime +def print_status(client): + print("") + for relay in client.relay_manager.relays.values(): + connected_text = "🟢" if relay.connected else "🔴" + status_text = f"{connected_text} ⬆️ {relay.num_sent_events} ⬇️ {relay.num_received_events} ⚠️ {relay.error_counter} ⏱️ {relay.ping} ms - {relay.url.split('//')[1]}" + print(status_text) + + async def dm(): print("This is an example NIP-04 DM flow") pk = input("Enter your privatekey to post from (enter nothing for a random one): ") @@ -36,6 +44,7 @@ def callback(event: Event, decrypted_content): ) print(f"Subscribing to DMs to {to_pubk_hex}") while True: + print_status(client) msg = input("\nEnter message: ") client.dm(msg, PublicKey(bytes.fromhex(to_pubk_hex))) @@ -84,11 +93,12 @@ def callback(event: Event): t.start() while True: + print_status(sender_client) msg = input("\nEnter post: ") sender_client.post(msg) -if input("Enter 1 for DM, 2 for Posts (Default: 1)") or 1 == 1: +if input("Enter '1' for DM, '2' for Posts (Default: 1):") or 1 == 1: # write a DM and receive DMs asyncio.run(dm()) else: diff --git a/nostr/event.py b/nostr/event.py index 11f56c6..9743e9f 100644 --- a/nostr/event.py +++ b/nostr/event.py @@ -6,8 +6,7 @@ from secp256k1 import PrivateKey, PublicKey from hashlib import sha256 -from nostr.message_type import ClientMessageType - +from .message_type import ClientMessageType class EventKind(IntEnum): @@ -19,17 +18,17 @@ class EventKind(IntEnum): DELETE = 5 - @dataclass class Event: content: str = None public_key: str = None created_at: int = None kind: int = EventKind.TEXT_NOTE - tags: List[List[str]] = field(default_factory=list) # Dataclasses require special handling when the default value is a mutable type + tags: List[List[str]] = field( + default_factory=list + ) # Dataclasses require special handling when the default value is a mutable type signature: str = None - def __post_init__(self): if self.content is not None and not isinstance(self.content, str): # DMs initialize content to None but all other kinds should pass in a str @@ -38,39 +37,44 @@ def __post_init__(self): if self.created_at is None: self.created_at = int(time.time()) - @staticmethod - def serialize(public_key: str, created_at: int, kind: int, tags: List[List[str]], content: str) -> bytes: + def serialize( + public_key: str, created_at: int, kind: int, tags: List[List[str]], content: str + ) -> bytes: data = [0, public_key, created_at, kind, tags, content] - data_str = json.dumps(data, separators=(',', ':'), ensure_ascii=False) + data_str = json.dumps(data, separators=(",", ":"), ensure_ascii=False) return data_str.encode() - @staticmethod - def compute_id(public_key: str, created_at: int, kind: int, tags: List[List[str]], content: str): - return sha256(Event.serialize(public_key, created_at, kind, tags, content)).hexdigest() - + def compute_id( + public_key: str, created_at: int, kind: int, tags: List[List[str]], content: str + ): + return sha256( + Event.serialize(public_key, created_at, kind, tags, content) + ).hexdigest() @property def id(self) -> str: # Always recompute the id to reflect the up-to-date state of the Event - return Event.compute_id(self.public_key, self.created_at, self.kind, self.tags, self.content) - - - def add_pubkey_ref(self, pubkey:str): - """ Adds a reference to a pubkey as a 'p' tag """ - self.tags.append(['p', pubkey]) - + return Event.compute_id( + self.public_key, self.created_at, self.kind, self.tags, self.content + ) - def add_event_ref(self, event_id:str): - """ Adds a reference to an event_id as an 'e' tag """ - self.tags.append(['e', event_id]) + def add_pubkey_ref(self, pubkey: str): + """Adds a reference to a pubkey as a 'p' tag""" + self.tags.append(["p", pubkey]) + def add_event_ref(self, event_id: str): + """Adds a reference to an event_id as an 'e' tag""" + self.tags.append(["e", event_id]) def verify(self) -> bool: - pub_key = PublicKey(bytes.fromhex("02" + self.public_key), True) # add 02 for schnorr (bip340) - return pub_key.schnorr_verify(bytes.fromhex(self.id), bytes.fromhex(self.signature), None, raw=True) - + pub_key = PublicKey( + bytes.fromhex("02" + self.public_key), True + ) # add 02 for schnorr (bip340) + return pub_key.schnorr_verify( + bytes.fromhex(self.id), bytes.fromhex(self.signature), None, raw=True + ) def to_message(self) -> str: return json.dumps( @@ -83,20 +87,18 @@ def to_message(self) -> str: "kind": self.kind, "tags": self.tags, "content": self.content, - "sig": self.signature - } + "sig": self.signature, + }, ] ) - @dataclass class EncryptedDirectMessage(Event): recipient_pubkey: str = None cleartext_content: str = None reference_event_id: str = None - def __post_init__(self): if self.content is not None: self.cleartext_content = self.content @@ -115,9 +117,10 @@ def __post_init__(self): if self.reference_event_id is not None: self.add_event_ref(self.reference_event_id) - @property def id(self) -> str: if self.content is None: - raise Exception("EncryptedDirectMessage `id` is undefined until its message is encrypted and stored in the `content` field") + raise Exception( + "EncryptedDirectMessage `id` is undefined until its message is encrypted and stored in the `content` field" + ) return super().id diff --git a/nostr/relay.py b/nostr/relay.py index bccbcad..97aa9ba 100644 --- a/nostr/relay.py +++ b/nostr/relay.py @@ -78,10 +78,8 @@ def check_reconnect(self): @property def ping(self): - if self.connected: - return int((self.ws.last_pong_tm - self.ws.last_ping_tm) * 1000) - else: - return 0 + ping_ms = int((self.ws.last_pong_tm - self.ws.last_ping_tm) * 1000) + return ping_ms if self.connected and ping_ms > 0 else 0 def publish(self, message: str): self.queue.put(message) From 557a01697e11c0be27b26d2d4c037fd9803f2f8c Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 8 Feb 2023 10:10:12 +0100 Subject: [PATCH 28/40] relative import --- nostr/event.py | 2 +- nostr/key.py | 43 +++++++++++++++++++++++++++++-------------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/nostr/event.py b/nostr/event.py index 9743e9f..b903e0e 100644 --- a/nostr/event.py +++ b/nostr/event.py @@ -3,7 +3,7 @@ from dataclasses import dataclass, field from enum import IntEnum from typing import List -from secp256k1 import PrivateKey, PublicKey +from secp256k1 import PublicKey from hashlib import sha256 from .message_type import ClientMessageType diff --git a/nostr/key.py b/nostr/key.py index 350c72d..d34697f 100644 --- a/nostr/key.py +++ b/nostr/key.py @@ -6,8 +6,8 @@ from cryptography.hazmat.primitives import padding from hashlib import sha256 -from nostr.delegation import Delegation -from nostr.event import EncryptedDirectMessage, Event, EventKind +from .delegation import Delegation +from .event import EncryptedDirectMessage, Event, EventKind from . import bech32 @@ -28,14 +28,14 @@ def verify_signed_message_hash(self, hash: str, sig: str) -> bool: @classmethod def from_npub(cls, npub: str): - """ Load a PublicKey from its bech32/npub form """ + """Load a PublicKey from its bech32/npub form""" hrp, data, spec = bech32.bech32_decode(npub) raw_public_key = bech32.convertbits(data, 5, 8)[:-1] return cls(bytes(raw_public_key)) class PrivateKey: - def __init__(self, raw_secret: bytes=None) -> None: + def __init__(self, raw_secret: bytes = None) -> None: if not raw_secret is None: self.raw_secret = raw_secret else: @@ -46,7 +46,7 @@ def __init__(self, raw_secret: bytes=None) -> None: @classmethod def from_nsec(cls, nsec: str): - """ Load a PrivateKey from its bech32/nsec form """ + """Load a PrivateKey from its bech32/nsec form""" hrp, data, spec = bech32.bech32_decode(nsec) raw_secret = bech32.convertbits(data, 5, 8)[:-1] return cls(bytes(raw_secret)) @@ -71,22 +71,28 @@ def encrypt_message(self, message: str, public_key_hex: str) -> str: padded_data = padder.update(message.encode()) + padder.finalize() iv = secrets.token_bytes(16) - cipher = Cipher(algorithms.AES(self.compute_shared_secret(public_key_hex)), modes.CBC(iv)) + cipher = Cipher( + algorithms.AES(self.compute_shared_secret(public_key_hex)), modes.CBC(iv) + ) encryptor = cipher.encryptor() encrypted_message = encryptor.update(padded_data) + encryptor.finalize() return f"{base64.b64encode(encrypted_message).decode()}?iv={base64.b64encode(iv).decode()}" - + def encrypt_dm(self, dm: EncryptedDirectMessage) -> None: - dm.content = self.encrypt_message(message=dm.cleartext_content, public_key_hex=dm.recipient_pubkey) + dm.content = self.encrypt_message( + message=dm.cleartext_content, public_key_hex=dm.recipient_pubkey + ) def decrypt_message(self, encoded_message: str, public_key_hex: str) -> str: - encoded_data = encoded_message.split('?iv=') + encoded_data = encoded_message.split("?iv=") encoded_content, encoded_iv = encoded_data[0], encoded_data[1] iv = base64.b64decode(encoded_iv) - cipher = Cipher(algorithms.AES(self.compute_shared_secret(public_key_hex)), modes.CBC(iv)) + cipher = Cipher( + algorithms.AES(self.compute_shared_secret(public_key_hex)), modes.CBC(iv) + ) encrypted_content = base64.b64decode(encoded_content) decryptor = cipher.decryptor() @@ -110,7 +116,9 @@ def sign_event(self, event: Event) -> None: event.signature = self.sign_message_hash(bytes.fromhex(event.id)) def sign_delegation(self, delegation: Delegation) -> None: - delegation.signature = self.sign_message_hash(sha256(delegation.delegation_token.encode()).digest()) + delegation.signature = self.sign_message_hash( + sha256(delegation.delegation_token.encode()).digest() + ) def __eq__(self, other): return self.raw_secret == other.raw_secret @@ -122,9 +130,12 @@ def mine_vanity_key(prefix: str = None, suffix: str = None) -> PrivateKey: while True: sk = PrivateKey() - if prefix is not None and not sk.public_key.bech32()[5:5+len(prefix)] == prefix: + if ( + prefix is not None + and not sk.public_key.bech32()[5 : 5 + len(prefix)] == prefix + ): continue - if suffix is not None and not sk.public_key.bech32()[-len(suffix):] == suffix: + if suffix is not None and not sk.public_key.bech32()[-len(suffix) :] == suffix: continue break @@ -132,7 +143,11 @@ def mine_vanity_key(prefix: str = None, suffix: str = None) -> PrivateKey: ffi = FFI() -@ffi.callback("int (unsigned char *, const unsigned char *, const unsigned char *, void *)") + + +@ffi.callback( + "int (unsigned char *, const unsigned char *, const unsigned char *, void *)" +) def copy_x(output, x32, y32, data): ffi.memmove(output, x32, 32) return 1 From 826dca7976f71e14baa532e1d58775ab26ab2cf3 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 8 Feb 2023 11:40:26 +0100 Subject: [PATCH 29/40] refactor --- nostr/client/client.py | 1 - nostr/relay_manager.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/nostr/client/client.py b/nostr/client/client.py index 86501b9..a669a23 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -44,7 +44,6 @@ def connect(self): self.relay_manager.open_connections( {"cert_reqs": ssl.CERT_NONE} ) # NOTE: This disables ssl certificate verification - self.relay_manager.start_message_workers() def close(self): self.relay_manager.close_connections() diff --git a/nostr/relay_manager.py b/nostr/relay_manager.py index 44d9e7b..6bcbee4 100644 --- a/nostr/relay_manager.py +++ b/nostr/relay_manager.py @@ -43,8 +43,6 @@ def open_connections(self, ssl_options: dict = None, proxy: dict = None): name=f"{relay.url}-thread", ).start() - def start_message_workers(self): - for relay in self.relays.values(): threading.Thread( target=relay.queue_worker, name=f"{relay.url}-queue", From d56f9caf474f4029d73d7a282b29628f3999c909 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 9 Feb 2023 09:01:03 +0100 Subject: [PATCH 30/40] update client --- main.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 3610848..1657e00 100644 --- a/main.py +++ b/main.py @@ -24,7 +24,7 @@ def callback(event: Event, decrypted_content): Callback to trigger when a DM is received. """ print( - f"From {event.public_key[:3]}..{event.public_key[-3:]}: {decrypted_content}" + f"\nFrom {event.public_key[:3]}..{event.public_key[-3:]}: {decrypted_content}" ) client = NostrClient(privatekey_hex=pk) @@ -45,6 +45,7 @@ def callback(event: Event, decrypted_content): print(f"Subscribing to DMs to {to_pubk_hex}") while True: print_status(client) + await asyncio.sleep(1) msg = input("\nEnter message: ") client.dm(msg, PublicKey(bytes.fromhex(to_pubk_hex))) @@ -57,7 +58,9 @@ def callback(event: Event): """ Callback to trigger when post appers. """ - print(f"From {event.public_key[:3]}..{event.public_key[-3:]}: {event.content}") + print( + f"\nFrom {event.public_key[:3]}..{event.public_key[-3:]}: {event.content}" + ) sender_client = NostrClient(privatekey_hex=pk) # await asyncio.sleep(1) @@ -94,6 +97,7 @@ def callback(event: Event): while True: print_status(sender_client) + await asyncio.sleep(1) msg = input("\nEnter post: ") sender_client.post(msg) From 56fff73af4ade90a9ba0895a665a0b597be36c45 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 9 Feb 2023 09:58:35 +0100 Subject: [PATCH 31/40] connection state on connect --- nostr/client/client.py | 2 +- nostr/relay.py | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/nostr/client/client.py b/nostr/client/client.py index a669a23..5517207 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -24,7 +24,7 @@ class NostrClient: "wss://lnbits.link/nostrrelay/client", "wss://nostr-pub.wellorder.net", "wss://nostr.zebedee.cloud", - "wss://no.str.cr", + "wss://nodestr.fmt.wiz.biz", ] # ["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 diff --git a/nostr/relay.py b/nostr/relay.py index 97aa9ba..ee78baa 100644 --- a/nostr/relay.py +++ b/nostr/relay.py @@ -55,13 +55,14 @@ def __init__( def connect(self, ssl_options: dict = None, proxy: dict = None): self.ssl_options = ssl_options self.proxy = proxy - self.ws.run_forever( - sslopt=ssl_options, - http_proxy_host=None if proxy is None else proxy.get("host"), - http_proxy_port=None if proxy is None else proxy.get("port"), - proxy_type=None if proxy is None else proxy.get("type"), - ping_interval=5, - ) + if not self.connected: + self.ws.run_forever( + sslopt=ssl_options, + http_proxy_host=None if proxy is None else proxy.get("host"), + http_proxy_port=None if proxy is None else proxy.get("port"), + proxy_type=None if proxy is None else proxy.get("type"), + ping_interval=5, + ) def close(self): self.ws.close() From be948b6ca3c86ae238a511b58265f0393e6f35ca Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 9 Feb 2023 11:55:12 +0100 Subject: [PATCH 32/40] subscribe --- main.py | 5 +++++ nostr/client/client.py | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 1657e00..436eaa1 100644 --- a/main.py +++ b/main.py @@ -28,6 +28,11 @@ def callback(event: Event, decrypted_content): ) client = NostrClient(privatekey_hex=pk) + if not pk: + print(f"Your private key: {client.private_key.hex()}") + + print(f"Your public key: {client.public_key.hex()}") + # await asyncio.sleep(1) t = threading.Thread( target=client.get_dm, diff --git a/nostr/client/client.py b/nostr/client/client.py index 5517207..5431152 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -129,9 +129,10 @@ def get_dm(self, sender_publickey: PublicKey, callback_func=None): break time.sleep(0.1) - async def subscribe(self): + async def subscribe(self, callback_func=None): while True: while self.relay_manager.message_pool.has_events(): event_msg = self.relay_manager.message_pool.get_event() - break + if callback_func: + callback_func(event_msg.event) time.sleep(0.1) From f598039e440f1d57c3b5d993ff44473649ffac3d Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 9 Feb 2023 11:57:51 +0100 Subject: [PATCH 33/40] fix --- nostr/client/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nostr/client/client.py b/nostr/client/client.py index 5431152..169ccc1 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -129,7 +129,7 @@ def get_dm(self, sender_publickey: PublicKey, callback_func=None): break time.sleep(0.1) - async def subscribe(self, callback_func=None): + def subscribe(self, callback_func=None): while True: while self.relay_manager.message_pool.has_events(): event_msg = self.relay_manager.message_pool.get_event() From 0328fc35f5db36ed4f747a7e3be3189ae617ff96 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 13 Feb 2023 22:11:23 +0100 Subject: [PATCH 34/40] bech32 working --- main.py | 43 ++++++++++++++++++++++++------------------ nostr/client/client.py | 14 +++++++++----- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/main.py b/main.py index 436eaa1..5232101 100644 --- a/main.py +++ b/main.py @@ -27,11 +27,11 @@ def callback(event: Event, decrypted_content): f"\nFrom {event.public_key[:3]}..{event.public_key[-3:]}: {decrypted_content}" ) - client = NostrClient(privatekey_hex=pk) + client = NostrClient(private_key=pk) if not pk: - print(f"Your private key: {client.private_key.hex()}") + print(f"Your private key: {client.private_key.bech32()}") - print(f"Your public key: {client.public_key.hex()}") + print(f"Your public key: {client.public_key.bech32()}") # await asyncio.sleep(1) t = threading.Thread( @@ -43,16 +43,20 @@ def callback(event: Event, decrypted_content): ) t.start() - to_pubk_hex = ( - input("Enter other pubkey to post to (enter nothing to DM yourself): ") + pubkey_to_str = ( + input("Enter other pubkey to DM to (enter nothing to DM yourself): ") or client.public_key.hex() ) - print(f"Subscribing to DMs to {to_pubk_hex}") + if pubkey_to_str.startswith("npub"): + pubkey_to = PublicKey().from_npub(pubkey_to_str) + else: + pubkey_to = PublicKey(bytes.fromhex(pubkey_to_str)) + print(f"Sending DMs to {pubkey_to.bech32()}") while True: print_status(client) await asyncio.sleep(1) msg = input("\nEnter message: ") - client.dm(msg, PublicKey(bytes.fromhex(to_pubk_hex))) + client.dm(msg, pubkey_to) async def post(): @@ -67,20 +71,23 @@ def callback(event: Event): f"\nFrom {event.public_key[:3]}..{event.public_key[-3:]}: {event.content}" ) - sender_client = NostrClient(privatekey_hex=pk) + sender_client = NostrClient(private_key=pk) # await asyncio.sleep(1) - to_pubk_hex = ( + pubkey_to_str = ( input( "Enter other pubkey (enter nothing to read your own posts, enter * for all): " ) or sender_client.public_key.hex() ) - if to_pubk_hex == "*": - to_pubk = None + if pubkey_to_str == "*": + pubkey_to = None + elif pubkey_to_str.startswith("npub"): + pubkey_to = PublicKey().from_npub(pubkey_to_str) else: - print(f"Subscribing to posts by {to_pubk_hex}") - to_pubk = PublicKey(bytes.fromhex(to_pubk_hex)) + pubkey_to = PublicKey(bytes.fromhex(pubkey_to_str)) + + print(f"Subscribing to posts by {pubkey_to.bech32() if pubkey_to else 'everyone'}") filters = { "since": int( @@ -93,7 +100,7 @@ def callback(event: Event): t = threading.Thread( target=sender_client.get_post, args=( - to_pubk, + pubkey_to, callback, filters, ), @@ -107,9 +114,9 @@ def callback(event: Event): sender_client.post(msg) -if input("Enter '1' for DM, '2' for Posts (Default: 1):") or 1 == 1: - # write a DM and receive DMs - asyncio.run(dm()) -else: +if input("Enter '1' for DM, '2' for Posts (Default: 1): ") == "2": # make a post and subscribe to posts asyncio.run(post()) +else: + # write a DM and receive DMs + asyncio.run(dm()) diff --git a/nostr/client/client.py b/nostr/client/client.py index 169ccc1..0c246d8 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -30,8 +30,8 @@ class NostrClient: private_key: PrivateKey public_key: PublicKey - def __init__(self, privatekey_hex: str = "", relays: List[str] = [], connect=True): - self.generate_keys(privatekey_hex) + def __init__(self, private_key: str = "", relays: List[str] = [], connect=True): + self.generate_keys(private_key) if len(relays): self.relays = relays @@ -48,9 +48,13 @@ def connect(self): def close(self): self.relay_manager.close_connections() - def generate_keys(self, privatekey_hex: str = None): - pk = bytes.fromhex(privatekey_hex) if privatekey_hex else None - self.private_key = PrivateKey(pk) + 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 self.public_key = self.private_key.public_key def post(self, message: str): From 383f49e0b23f914378f386e77ee21114262652dc Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 13 Feb 2023 22:22:50 +0100 Subject: [PATCH 35/40] fix default pubkey --- nostr/key.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nostr/key.py b/nostr/key.py index d34697f..6988964 100644 --- a/nostr/key.py +++ b/nostr/key.py @@ -12,7 +12,7 @@ class PublicKey: - def __init__(self, raw_bytes: bytes) -> None: + def __init__(self, raw_bytes: bytes = None) -> None: self.raw_bytes = raw_bytes def bech32(self) -> str: From 2872fe3c24ad0036e1bcf262790930d9a1e65d70 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 13 Feb 2023 23:02:36 +0100 Subject: [PATCH 36/40] filter for DM --- main.py | 6 +----- nostr/client/client.py | 3 ++- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/main.py b/main.py index 5232101..08a3c61 100644 --- a/main.py +++ b/main.py @@ -33,13 +33,9 @@ def callback(event: Event, decrypted_content): print(f"Your public key: {client.public_key.bech32()}") - # await asyncio.sleep(1) t = threading.Thread( target=client.get_dm, - args=( - client.public_key, - callback, - ), + args=(client.public_key, callback), ) t.start() diff --git a/nostr/client/client.py b/nostr/client/client.py index 0c246d8..52676e0 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -96,12 +96,13 @@ def dm(self, message: str, to_pubkey: PublicKey): self.private_key.sign_event(dm) self.relay_manager.publish_event(dm) - def get_dm(self, sender_publickey: PublicKey, callback_func=None): + def get_dm(self, sender_publickey: PublicKey, callback_func=None, filter_kwargs={}): filters = Filters( [ Filter( kinds=[EventKind.ENCRYPTED_DIRECT_MESSAGE], pubkey_refs=[sender_publickey.hex()], + **filter_kwargs, ) ] ) From 1f5c6f65d1444c4b25cd5c2e0ac4fa4b6fd59dc3 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 25 Feb 2023 12:17:34 +0100 Subject: [PATCH 37/40] threads are daemon mode --- main.py | 4 ++-- nostr/relay_manager.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index 08a3c61..a3c489b 100644 --- a/main.py +++ b/main.py @@ -34,8 +34,7 @@ def callback(event: Event, decrypted_content): print(f"Your public key: {client.public_key.bech32()}") t = threading.Thread( - target=client.get_dm, - args=(client.public_key, callback), + target=client.get_dm, args=(client.public_key, callback), daemon=True ) t.start() @@ -100,6 +99,7 @@ def callback(event: Event): callback, filters, ), + daemon=True, ) t.start() diff --git a/nostr/relay_manager.py b/nostr/relay_manager.py index 6bcbee4..5b92d8d 100644 --- a/nostr/relay_manager.py +++ b/nostr/relay_manager.py @@ -41,11 +41,11 @@ def open_connections(self, ssl_options: dict = None, proxy: dict = None): target=relay.connect, args=(ssl_options, proxy), name=f"{relay.url}-thread", + daemon=True, ).start() threading.Thread( - target=relay.queue_worker, - name=f"{relay.url}-queue", + target=relay.queue_worker, name=f"{relay.url}-queue", daemon=True ).start() def close_connections(self): From 880dd1183a03d0aa42e66ce418b782b06d8ce519 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 25 Feb 2023 13:19:52 +0100 Subject: [PATCH 38/40] add default relays --- nostr/client/client.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/nostr/client/client.py b/nostr/client/client.py index 52676e0..8158979 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -21,10 +21,15 @@ class NostrClient: relays = [ - "wss://lnbits.link/nostrrelay/client", "wss://nostr-pub.wellorder.net", + "wss://relay.damus.io", "wss://nostr.zebedee.cloud", - "wss://nodestr.fmt.wiz.biz", + "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" relay_manager = RelayManager() private_key: PrivateKey From 8086e4d8ba1b6a0a9b4dffdf828aeaba97fc94e2 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 14 Apr 2023 13:50:30 +0200 Subject: [PATCH 39/40] update client for debugging --- nostr/client/client.py | 45 ++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/nostr/client/client.py b/nostr/client/client.py index 8158979..74bafea 100644 --- a/nostr/client/client.py +++ b/nostr/client/client.py @@ -21,15 +21,17 @@ class NostrClient: relays = [ - "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://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.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 @@ -85,6 +87,7 @@ 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: @@ -99,6 +102,7 @@ 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={}): @@ -118,7 +122,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() @@ -139,10 +143,25 @@ def get_dm(self, sender_publickey: PublicKey, callback_func=None, filter_kwargs= break time.sleep(0.1) - def subscribe(self, callback_func=None): + def subscribe( + self, + callback_events_func=None, + callback_notices_func=None, + callback_eosenotices_func=None, + ): while True: while self.relay_manager.message_pool.has_events(): event_msg = self.relay_manager.message_pool.get_event() - if callback_func: - callback_func(event_msg.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() + if callback_notices_func: + callback_notices_func(event_msg) + while self.relay_manager.message_pool.has_eose_notices(): + event_msg = self.relay_manager.message_pool.get_eose_notice() + if callback_eosenotices_func: + callback_eosenotices_func(event_msg) + time.sleep(0.1) From 48919231d66dc6ebe85d6835159fc445fd4a7ddb Mon Sep 17 00:00:00 2001 From: Vic <88121568+vicariousdrama@users.noreply.github.com> Date: Thu, 12 Oct 2023 12:55:56 -0400 Subject: [PATCH 40/40] Update relay.py Avoid failure if the subscription_id referenced is no longer present in subscriptions --- nostr/relay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nostr/relay.py b/nostr/relay.py index ee78baa..670480c 100644 --- a/nostr/relay.py +++ b/nostr/relay.py @@ -100,7 +100,7 @@ def add_subscription(self, id, filters: Filters): def close_subscription(self, id: str) -> None: with self.lock: - self.subscriptions.pop(id) + self.subscriptions.pop(id, None) def update_subscription(self, id: str, filters: Filters) -> None: with self.lock: