- {{SITE_TITLE}} Nostrclient Extension
+ Nostrclient Extension
This extension is a always-on nostr client that other extensions can
- use to send and receive events on nostr.
-
+ use to send and receive events on nostr.
+
Add multiple nostr relays to connect to. The extension then opens a websocket for you to use
at
@@ -197,10 +197,8 @@
)
.then(function (response) {
if (response.data) {
- console.log(response.data)
response.data.map(maplrelays)
self.nostrrelayLinks = response.data
- console.log(self.nostrrelayLinks)
}
})
.catch(function (error) {
@@ -219,10 +217,10 @@
message: `Invalid relay URL.`,
caption: "Should start with 'wss://'' or 'ws://'"
})
- return
+ return false;
}
console.log('ADD RELAY ' + this.relayToAdd)
- var self = this
+ let that = this
LNbits.api
.request(
'POST',
@@ -231,17 +229,17 @@
{url: this.relayToAdd}
)
.then(function (response) {
+ console.log("response:", response)
if (response.data) {
- console.log(response.data)
response.data.map(maplrelays)
- self.nostrrelayLinks = response.data
- console.log(self.nostrrelayLinks)
+ that.nostrrelayLinks = response.data
+ that.relayToAdd = ''
}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
- location.reload()
+ return false;
},
deleteRelay(url) {
console.log('DELETE RELAY ' + url)
@@ -260,7 +258,6 @@
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
- location.reload()
},
exportlnurldeviceCSV: function () {
var self = this
diff --git a/views.py b/views.py
index 90dd31c..7f07f5d 100644
--- a/views.py
+++ b/views.py
@@ -1,20 +1,18 @@
-from http import HTTPStatus
import asyncio
+from http import HTTPStatus
+
+# FastAPI good for incoming
from fastapi import Request
from fastapi.param_functions import Query
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates
-from starlette.exceptions import HTTPException
-from starlette.responses import HTMLResponse
-from . import nostrclient_ext, nostr_renderer
-
-# FastAPI good for incoming
-from fastapi import Request
from lnbits.core.crud import update_payment_status
from lnbits.core.models import User
from lnbits.core.views.api import api_payment
-from lnbits.decorators import check_user_exists, check_admin
+from lnbits.decorators import check_admin, check_user_exists
+from starlette.responses import HTMLResponse
+from . import nostr_renderer, nostrclient_ext
templates = Jinja2Templates(directory="templates")
diff --git a/views_api.py b/views_api.py
index d2c74fe..1cf7a19 100644
--- a/views_api.py
+++ b/views_api.py
@@ -1,33 +1,25 @@
-from http import HTTPStatus
import asyncio
-from fastapi import WebSocket
-from fastapi.params import Depends
+from http import HTTPStatus
+from typing import Optional
+
+from fastapi import Depends, WebSocket
+from lnbits.decorators import check_admin
+from lnbits.helpers import urlsafe_short_hash
+from loguru import logger
+from starlette.exceptions import HTTPException
from . import nostrclient_ext
-from .tasks import client
-from loguru import logger
-
-from .crud import get_relays, add_relay, delete_relay
-from .models import RelayList, Relay
-
+from .crud import add_relay, delete_relay, get_relays
+from .models import Relay, RelayList
from .services import NostrRouter
-
-from lnbits.decorators import (
- WalletTypeInfo,
- get_key_type,
- require_admin_key,
- check_admin,
-)
-
-from lnbits.helpers import urlsafe_short_hash
-from .tasks import init_relays
+from .tasks import client, init_relays
# we keep this in
all_routers: list[NostrRouter] = []
@nostrclient_ext.get("/api/v1/relays")
-async def api_get_relays(): # type: ignore
+async def api_get_relays() -> RelayList:
relays = RelayList(__root__=[])
for url, r in client.relay_manager.relays.items():
status_text = (
@@ -52,20 +44,30 @@ async def api_get_relays(): # type: ignore
@nostrclient_ext.post(
"/api/v1/relay", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
)
-async def api_add_relay(relay: Relay): # type: ignore
- assert relay.url, "no URL"
+async def api_add_relay(relay: Relay) -> Optional[RelayList]:
+ if not relay.url:
+ raise HTTPException(
+ status_code=HTTPStatus.BAD_REQUEST, detail=f"Relay url not provided."
+ )
if relay.url in client.relay_manager.relays:
- return
+ raise HTTPException(
+ status_code=HTTPStatus.BAD_REQUEST,
+ detail=f"Relay: {relay.url} already exists.",
+ )
relay.id = urlsafe_short_hash()
await add_relay(relay)
await init_relays()
+ return await get_relays()
@nostrclient_ext.delete(
"/api/v1/relay", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
)
-async def api_delete_relay(relay: Relay): # type: ignore
- assert relay.url
+async def api_delete_relay(relay: Relay) -> None:
+ if not relay.url:
+ raise HTTPException(
+ status_code=HTTPStatus.BAD_REQUEST, detail=f"Relay url not provided."
+ )
client.relay_manager.remove_relay(relay.url)
await delete_relay(relay)
@@ -91,7 +93,7 @@ async def api_stop():
@nostrclient_ext.websocket("/api/v1/relay")
-async def ws_relay(websocket: WebSocket):
+async def ws_relay(websocket: WebSocket) -> None:
"""Relay multiplexer: one client (per endpoint) <-> multiple relays"""
await websocket.accept()
router = NostrRouter(websocket)
From 4549d0af092846974ea615a3396c1a106b196a5f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Sun, 19 Mar 2023 11:15:26 +0100
Subject: [PATCH 006/100] revert formatting nostr
---
nostr/bech32.py | 33 ++++++++++-----------------------
nostr/client/cbc.py | 10 +++++-----
nostr/client/client.py | 21 +++++++++++++--------
nostr/delegation.py | 8 ++++----
nostr/event.py | 5 ++---
nostr/key.py | 9 ++++-----
nostr/message_pool.py | 3 +--
nostr/message_type.py | 9 ++-------
nostr/pow.py | 11 ++---------
nostr/relay.py | 2 --
nostr/subscription.py | 8 +++++---
11 files changed, 48 insertions(+), 71 deletions(-)
diff --git a/nostr/bech32.py b/nostr/bech32.py
index 0ae6c80..b068de7 100644
--- a/nostr/bech32.py
+++ b/nostr/bech32.py
@@ -23,25 +23,21 @@
from enum import Enum
-
class Encoding(Enum):
"""Enumeration type to list the various supported encodings."""
-
BECH32 = 1
BECH32M = 2
-
CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
-BECH32M_CONST = 0x2BC830A3
-
+BECH32M_CONST = 0x2bc830a3
def bech32_polymod(values):
"""Internal function that computes the Bech32 checksum."""
- generator = [0x3B6A57B2, 0x26508E6D, 0x1EA119FA, 0x3D4233DD, 0x2A1462B3]
+ generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
chk = 1
for value in values:
top = chk >> 25
- chk = (chk & 0x1FFFFFF) << 5 ^ value
+ chk = (chk & 0x1ffffff) << 5 ^ value
for i in range(5):
chk ^= generator[i] if ((top >> i) & 1) else 0
return chk
@@ -61,7 +57,6 @@ def bech32_verify_checksum(hrp, data):
return Encoding.BECH32M
return None
-
def bech32_create_checksum(hrp, data, spec):
"""Compute the checksum values given HRP and data."""
values = bech32_hrp_expand(hrp) + data
@@ -73,29 +68,26 @@ def bech32_create_checksum(hrp, data, spec):
def bech32_encode(hrp, data, spec):
"""Compute a Bech32 string given HRP and data values."""
combined = data + bech32_create_checksum(hrp, data, spec)
- return hrp + "1" + "".join([CHARSET[d] for d in combined])
-
+ return hrp + '1' + ''.join([CHARSET[d] for d in combined])
def bech32_decode(bech):
"""Validate a Bech32/Bech32m string, and determine HRP and data."""
- if (any(ord(x) < 33 or ord(x) > 126 for x in bech)) or (
- bech.lower() != bech and bech.upper() != bech
- ):
+ if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or
+ (bech.lower() != bech and bech.upper() != bech)):
return (None, None, None)
bech = bech.lower()
- pos = bech.rfind("1")
+ pos = bech.rfind('1')
if pos < 1 or pos + 7 > len(bech) or len(bech) > 90:
return (None, None, None)
- if not all(x in CHARSET for x in bech[pos + 1 :]):
+ if not all(x in CHARSET for x in bech[pos+1:]):
return (None, None, None)
hrp = bech[:pos]
- data = [CHARSET.find(x) for x in bech[pos + 1 :]]
+ data = [CHARSET.find(x) for x in bech[pos+1:]]
spec = bech32_verify_checksum(hrp, data)
if spec is None:
return (None, None, None)
return (hrp, data[:-6], spec)
-
def convertbits(data, frombits, tobits, pad=True):
"""General power-of-2 base conversion."""
acc = 0
@@ -131,12 +123,7 @@ def decode(hrp, addr):
return (None, None)
if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32:
return (None, None)
- if (
- data[0] == 0
- and spec != Encoding.BECH32
- or data[0] != 0
- and spec != Encoding.BECH32M
- ):
+ if data[0] == 0 and spec != Encoding.BECH32 or data[0] != 0 and spec != Encoding.BECH32M:
return (None, None)
return (data[0], decoded)
diff --git a/nostr/client/cbc.py b/nostr/client/cbc.py
index e69e8b5..a41dbc0 100644
--- a/nostr/client/cbc.py
+++ b/nostr/client/cbc.py
@@ -1,3 +1,4 @@
+
from Cryptodome import Random
from Cryptodome.Cipher import AES
@@ -10,10 +11,10 @@ key = bytes.fromhex("3aa925cb69eb613e2928f8a18279c78b1dca04541dfd064df2eda66b598
BLOCK_SIZE = 16
-
class AESCipher(object):
- """This class is compatible with crypto.createCipheriv('aes-256-cbc')"""
+ """This class is compatible with crypto.createCipheriv('aes-256-cbc')
+ """
def __init__(self, key=None):
self.key = key
@@ -32,10 +33,9 @@ class AESCipher(object):
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)
+ print(dec_text)
\ No newline at end of file
diff --git a/nostr/client/client.py b/nostr/client/client.py
index 5be6089..6fb885f 100644
--- a/nostr/client/client.py
+++ b/nostr/client/client.py
@@ -1,15 +1,20 @@
-import base64
-import json
-import os
+from typing import *
import ssl
import time
-from typing import *
+import json
+import os
+import base64
-from ..event import EncryptedDirectMessage, Event, EventKind
-from ..filter import Filter, Filters
-from ..key import PrivateKey, PublicKey
-from ..message_type import ClientMessageType
+from ..event import Event
from ..relay_manager import RelayManager
+from ..message_type import ClientMessageType
+from ..key import PrivateKey, PublicKey
+
+from ..filter import Filter, Filters
+from ..event import Event, EventKind, EncryptedDirectMessage
+from ..relay_manager import RelayManager
+from ..message_type import ClientMessageType
+
# from aes import AESCipher
from . import cbc
diff --git a/nostr/delegation.py b/nostr/delegation.py
index 8b1c311..94801f5 100644
--- a/nostr/delegation.py
+++ b/nostr/delegation.py
@@ -7,23 +7,23 @@ class Delegation:
delegator_pubkey: str
delegatee_pubkey: str
event_kind: int
- duration_secs: int = 30 * 24 * 60 # default to 30 days
+ duration_secs: int = 30*24*60 # default to 30 days
signature: str = None # set in PrivateKey.sign_delegation
@property
def expires(self) -> int:
return int(time.time()) + self.duration_secs
-
+
@property
def conditions(self) -> str:
return f"kind={self.event_kind}&created_at<{self.expires}"
-
+
@property
def delegation_token(self) -> str:
return f"nostr:delegation:{self.delegatee_pubkey}:{self.conditions}"
def get_tag(self) -> list[str]:
- """Called by Event"""
+ """ Called by Event """
return [
"delegation",
self.delegator_pubkey,
diff --git a/nostr/event.py b/nostr/event.py
index 65b187d..b903e0e 100644
--- a/nostr/event.py
+++ b/nostr/event.py
@@ -1,11 +1,10 @@
-import json
import time
+import json
from dataclasses import dataclass, field
from enum import IntEnum
-from hashlib import sha256
from typing import List
-
from secp256k1 import PublicKey
+from hashlib import sha256
from .message_type import ClientMessageType
diff --git a/nostr/key.py b/nostr/key.py
index 8089e11..d34697f 100644
--- a/nostr/key.py
+++ b/nostr/key.py
@@ -1,15 +1,14 @@
-import base64
import secrets
-from hashlib import sha256
-
+import base64
import secp256k1
from cffi import FFI
-from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+from cryptography.hazmat.primitives import padding
+from hashlib import sha256
-from . import bech32
from .delegation import Delegation
from .event import EncryptedDirectMessage, Event, EventKind
+from . import bech32
class PublicKey:
diff --git a/nostr/message_pool.py b/nostr/message_pool.py
index 373d9fc..d364cf2 100644
--- a/nostr/message_pool.py
+++ b/nostr/message_pool.py
@@ -1,9 +1,8 @@
import json
from queue import Queue
from threading import Lock
-
-from .event import Event
from .message_type import RelayMessageType
+from .event import Event
class EventMessage:
diff --git a/nostr/message_type.py b/nostr/message_type.py
index 9f3be78..3f5206b 100644
--- a/nostr/message_type.py
+++ b/nostr/message_type.py
@@ -3,7 +3,6 @@ class ClientMessageType:
REQUEST = "REQ"
CLOSE = "CLOSE"
-
class RelayMessageType:
EVENT = "EVENT"
NOTICE = "NOTICE"
@@ -11,10 +10,6 @@ class RelayMessageType:
@staticmethod
def is_valid(type: str) -> bool:
- if (
- type == RelayMessageType.EVENT
- or type == RelayMessageType.NOTICE
- or type == RelayMessageType.END_OF_STORED_EVENTS
- ):
+ if type == RelayMessageType.EVENT or type == RelayMessageType.NOTICE or type == RelayMessageType.END_OF_STORED_EVENTS:
return True
- return False
+ return False
\ No newline at end of file
diff --git a/nostr/pow.py b/nostr/pow.py
index 034ad9a..e006288 100644
--- a/nostr/pow.py
+++ b/nostr/pow.py
@@ -1,9 +1,7 @@
import time
-
from .event import Event
from .key import PrivateKey
-
def zero_bits(b: int) -> int:
n = 0
@@ -16,11 +14,10 @@ def zero_bits(b: int) -> int:
return 7 - n
-
def count_leading_zero_bits(hex_str: str) -> int:
total = 0
for i in range(0, len(hex_str) - 2, 2):
- bits = zero_bits(int(hex_str[i : i + 2], 16))
+ bits = zero_bits(int(hex_str[i:i+2], 16))
total += bits
if bits != 8:
@@ -28,10 +25,7 @@ def count_leading_zero_bits(hex_str: str) -> int:
return total
-
-def mine_event(
- content: str, difficulty: int, public_key: str, kind: int, tags: list = []
-) -> Event:
+def mine_event(content: str, difficulty: int, public_key: str, kind: int, tags: list=[]) -> Event:
all_tags = [["nonce", "1", str(difficulty)]]
all_tags.extend(tags)
@@ -49,7 +43,6 @@ def mine_event(
return Event(public_key, content, created_at, kind, all_tags, event_id)
-
def mine_key(difficulty: int) -> PrivateKey:
sk = PrivateKey()
num_leading_zero_bits = count_leading_zero_bits(sk.public_key.hex())
diff --git a/nostr/relay.py b/nostr/relay.py
index 0851c02..ee78baa 100644
--- a/nostr/relay.py
+++ b/nostr/relay.py
@@ -2,9 +2,7 @@ import json
import time
from queue import Queue
from threading import Lock
-
from websocket import WebSocketApp
-
from .event import Event
from .filter import Filters
from .message_pool import MessagePool
diff --git a/nostr/subscription.py b/nostr/subscription.py
index 10b5363..7afba20 100644
--- a/nostr/subscription.py
+++ b/nostr/subscription.py
@@ -1,10 +1,12 @@
from .filter import Filters
-
class Subscription:
- def __init__(self, id: str, filters: Filters = None) -> None:
+ def __init__(self, id: str, filters: Filters=None) -> None:
self.id = id
self.filters = filters
def to_json_object(self):
- return {"id": self.id, "filters": self.filters.to_json_array()}
+ return {
+ "id": self.id,
+ "filters": self.filters.to_json_array()
+ }
From d67133ae61ef08d1f27c2987d56423312444cb32 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Tue, 21 Mar 2023 16:17:04 +0100
Subject: [PATCH 007/100] refactor nostrclient
---
__init__.py | 5 +---
services.py | 24 +++++++++++++----
tasks.py | 27 +++++++++----------
views.py | 74 ----------------------------------------------------
views_api.py | 18 +++++++------
5 files changed, 43 insertions(+), 105 deletions(-)
diff --git a/__init__.py b/__init__.py
index ace3254..29f5658 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,3 +1,4 @@
+import asyncio
from fastapi import APIRouter
from lnbits.db import Database
from lnbits.helpers import template_renderer
@@ -22,13 +23,9 @@ def nostr_renderer():
from .tasks import init_relays, subscribe_events
-from .views import * # noqa
-from .views_api import * # noqa
def nostrclient_start():
loop = asyncio.get_event_loop()
loop.create_task(catch_everything_and_restart(init_relays))
- # loop.create_task(catch_everything_and_restart(send_data))
- # loop.create_task(catch_everything_and_restart(receive_data))
loop.create_task(catch_everything_and_restart(subscribe_events))
diff --git a/services.py b/services.py
index 243af58..09e9235 100644
--- a/services.py
+++ b/services.py
@@ -4,14 +4,26 @@ from typing import List, Union
from fastapi import WebSocket, WebSocketDisconnect
from lnbits.helpers import urlsafe_short_hash
+from .nostr.client.client import NostrClient as NostrClientLib
from .models import Event, Filter, Filters, Relay, RelayList
from .nostr.event import Event as NostrEvent
from .nostr.filter import Filter as NostrFilter
from .nostr.filter import Filters as NostrFilters
-from .tasks import (client, received_event_queue,
- received_subscription_eosenotices,
- received_subscription_events)
+from .nostr.message_pool import EndOfStoredEventsMessage, EventMessage, NoticeMessage
+
+
+received_subscription_events: dict[str, list[Event]] = {}
+received_subscription_notices: dict[str, list[NoticeMessage]] = {}
+received_subscription_eosenotices: dict[str, EndOfStoredEventsMessage] = {}
+
+
+class NostrClient:
+ def __init__(self):
+ self.client: NostrClientLib = NostrClientLib(connect=False)
+
+
+nostr = NostrClient()
class NostrRouter:
@@ -44,7 +56,7 @@ class NostrRouter:
json_str = json_str_rewritten
# publish data
- client.relay_manager.publish_message(json_str)
+ nostr.client.relay_manager.publish_message(json_str)
async def nostr_to_client(self):
"""Sends responses from relays back to the client. Polls the subscriptions of this client
@@ -126,7 +138,9 @@ class NostrRouter:
)
fltr = json_data[2]
filters = self._marshall_nostr_filters(fltr)
- client.relay_manager.add_subscription(subscription_id_rewritten, filters)
+ nostr.client.relay_manager.add_subscription(
+ subscription_id_rewritten, filters
+ )
request_rewritten = json.dumps(["REQ", subscription_id_rewritten, fltr])
return subscription_id_rewritten, request_rewritten
return None, None
diff --git a/tasks.py b/tasks.py
index 8bbf8aa..7894e40 100644
--- a/tasks.py
+++ b/tasks.py
@@ -2,34 +2,33 @@ import asyncio
import ssl
import threading
-from .nostr.client.client import NostrClient
from .nostr.event import Event
from .nostr.key import PublicKey
-from .nostr.message_pool import (EndOfStoredEventsMessage, EventMessage,
- NoticeMessage)
+from .nostr.message_pool import EndOfStoredEventsMessage, EventMessage, NoticeMessage
from .nostr.relay_manager import RelayManager
-
-client = NostrClient(
- connect=False,
+from .services import (
+ nostr,
+ received_subscription_events,
+ received_subscription_eosenotices,
)
-received_event_queue: asyncio.Queue[EventMessage] = asyncio.Queue(0)
-received_subscription_events: dict[str, list[Event]] = {}
-received_subscription_notices: dict[str, list[NoticeMessage]] = {}
-received_subscription_eosenotices: dict[str, EndOfStoredEventsMessage] = {}
from .crud import get_relays
async def init_relays():
+ # reinitialize the entire client
+ nostr.__init__()
+ # get relays from db
relays = await get_relays()
- client.relays = list(set([r.url for r in relays.__root__ if r.url]))
- client.connect()
+ # set relays and connect to them
+ nostr.client.relays = list(set([r.url for r in relays.__root__ if r.url]))
+ nostr.client.connect()
return
async def subscribe_events():
- while not any([r.connected for r in client.relay_manager.relays.values()]):
+ while not any([r.connected for r in nostr.client.relay_manager.relays.values()]):
await asyncio.sleep(2)
def callback_events(eventMessage: EventMessage):
@@ -65,7 +64,7 @@ async def subscribe_events():
return
t = threading.Thread(
- target=client.subscribe,
+ target=nostr.client.subscribe,
args=(
callback_events,
callback_notices,
diff --git a/views.py b/views.py
index 7f07f5d..ce30b3b 100644
--- a/views.py
+++ b/views.py
@@ -22,77 +22,3 @@ async def index(request: Request, user: User = Depends(check_admin)):
return nostr_renderer().TemplateResponse(
"nostrclient/index.html", {"request": request, "user": user.dict()}
)
-
-
-# #####################################################################
-# #################### NOSTR WEBSOCKET THREAD #########################
-# ##### THE QUEUE LOOP THREAD THING THAT LISTENS TO BUNCH OF ##########
-# ### WEBSOCKET CONNECTIONS, STORING DATA IN DB/PUSHING TO FRONTEND ###
-# ################### VIA updater() FUNCTION ##########################
-# #####################################################################
-
-# websocket_queue = asyncio.Queue(1000)
-
-# # while True:
-# async def nostr_subscribe():
-# return
-# # for the relays:
-# # async with websockets.connect("ws://localhost:8765") as websocket:
-# # for the public keys:
-# # await websocket.send("subscribe to events")
-# # await websocket.recv()
-
-# #####################################################################
-# ################### LNBITS WEBSOCKET ROUTES #########################
-# #### HERE IS WHERE LNBITS FRONTEND CAN RECEIVE AND SEND MESSAGES ####
-# #####################################################################
-
-# class ConnectionManager:
-# def __init__(self):
-# self.active_connections: List[WebSocket] = []
-
-# async def connect(self, websocket: WebSocket, nostr_id: str):
-# await websocket.accept()
-# websocket.id = nostr_id
-# self.active_connections.append(websocket)
-
-# def disconnect(self, websocket: WebSocket):
-# self.active_connections.remove(websocket)
-
-# async def send_personal_message(self, message: str, nostr_id: str):
-# for connection in self.active_connections:
-# if connection.id == nostr_id:
-# await connection.send_text(message)
-
-# async def broadcast(self, message: str):
-# for connection in self.active_connections:
-# await connection.send_text(message)
-
-
-# manager = ConnectionManager()
-
-
-# @nostrclient_ext.websocket("/nostrclient/ws/relayevents/{nostr_id}", name="nostr_id.websocket_by_id")
-# async def websocket_endpoint(websocket: WebSocket, nostr_id: str):
-# await manager.connect(websocket, nostr_id)
-# try:
-# while True:
-# data = await websocket.receive_text()
-# except WebSocketDisconnect:
-# manager.disconnect(websocket)
-
-
-# async def updater(nostr_id, message):
-# copilot = await get_copilot(nostr_id)
-# if not copilot:
-# return
-# await manager.send_personal_message(f"{message}", nostr_id)
-
-
-# async def relay_check(relay: str):
-# async with websockets.connect(relay) as websocket:
-# if str(websocket.state) == "State.OPEN":
-# print(str(websocket.state))
-# return True
-# else:
-# return False
diff --git a/views_api.py b/views_api.py
index 1cf7a19..c3da200 100644
--- a/views_api.py
+++ b/views_api.py
@@ -11,8 +11,8 @@ from starlette.exceptions import HTTPException
from . import nostrclient_ext
from .crud import add_relay, delete_relay, get_relays
from .models import Relay, RelayList
-from .services import NostrRouter
-from .tasks import client, init_relays
+from .services import NostrRouter, nostr
+from .tasks import init_relays
# we keep this in
all_routers: list[NostrRouter] = []
@@ -21,7 +21,7 @@ all_routers: list[NostrRouter] = []
@nostrclient_ext.get("/api/v1/relays")
async def api_get_relays() -> RelayList:
relays = RelayList(__root__=[])
- for url, r in client.relay_manager.relays.items():
+ for url, r in nostr.client.relay_manager.relays.items():
status_text = (
f"⬆️ {r.num_sent_events} ⬇️ {r.num_received_events} ⚠️ {r.error_counter}"
)
@@ -49,13 +49,14 @@ async def api_add_relay(relay: Relay) -> Optional[RelayList]:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail=f"Relay url not provided."
)
- if relay.url in client.relay_manager.relays:
+ if relay.url in nostr.client.relay_manager.relays:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=f"Relay: {relay.url} already exists.",
)
relay.id = urlsafe_short_hash()
await add_relay(relay)
+ # we can't add relays during runtime yet
await init_relays()
return await get_relays()
@@ -68,7 +69,8 @@ async def api_delete_relay(relay: Relay) -> None:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail=f"Relay url not provided."
)
- client.relay_manager.remove_relay(relay.url)
+ # we can remove relays during runtime
+ nostr.client.relay_manager.remove_relay(relay.url)
await delete_relay(relay)
@@ -79,13 +81,13 @@ async def api_stop():
for router in all_routers:
try:
for s in router.subscriptions:
- client.relay_manager.close_subscription(s)
+ nostr.client.relay_manager.close_subscription(s)
await router.stop()
all_routers.remove(router)
except Exception as e:
logger.error(e)
try:
- client.relay_manager.close_connections()
+ nostr.client.relay_manager.close_connections()
except Exception as e:
logger.error(e)
@@ -106,7 +108,7 @@ async def ws_relay(websocket: WebSocket) -> None:
if not router.connected:
for s in router.subscriptions:
try:
- client.relay_manager.close_subscription(s)
+ nostr.client.relay_manager.close_subscription(s)
except:
pass
await router.stop()
From 2ff67b65e888f534a0a3d9e86e163bd56540aecd Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Tue, 21 Mar 2023 16:31:32 +0100
Subject: [PATCH 008/100] fix it
---
__init__.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/__init__.py b/__init__.py
index 29f5658..90da4ec 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,4 +1,3 @@
-import asyncio
from fastapi import APIRouter
from lnbits.db import Database
from lnbits.helpers import template_renderer
@@ -23,6 +22,8 @@ def nostr_renderer():
from .tasks import init_relays, subscribe_events
+from .views import * # noqa
+from .views_api import * # noqa
def nostrclient_start():
From 619e0f05e25ae46487a06db82ab9d0cec7ba0f20 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Thu, 23 Mar 2023 10:35:45 +0200
Subject: [PATCH 009/100] chore: code format
---
README.md | 2 --
__init__.py | 3 +-
crud.py | 1 +
models.py | 3 +-
nostr/bech32.py | 33 ++++++++++++++-------
nostr/client/cbc.py | 12 ++++----
nostr/client/client.py | 18 +++++------
nostr/delegation.py | 8 ++---
nostr/event.py | 7 +++--
nostr/key.py | 13 ++++----
nostr/message_pool.py | 3 +-
nostr/message_type.py | 9 ++++--
nostr/pow.py | 11 +++++--
nostr/relay.py | 2 ++
nostr/subscription.py | 8 ++---
services.py | 4 +--
tasks.py | 6 ++--
templates/nostrclient/index.html | 51 +++++++++++++++++++++-----------
views.py | 3 +-
views_api.py | 5 ++--
20 files changed, 121 insertions(+), 81 deletions(-)
diff --git a/README.md b/README.md
index e609b6c..5f9bfbc 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,4 @@
`nostrclient` is an always-on extension that can open multiple connections to nostr relays and act as a multiplexer for other clients: You open a single websocket to `nostrclient` which then sends the data to multiple relays. The responses from these relays are then sent back to the client.
-
-

diff --git a/__init__.py b/__init__.py
index 90da4ec..60d8e23 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,8 +1,9 @@
from fastapi import APIRouter
+from starlette.staticfiles import StaticFiles
+
from lnbits.db import Database
from lnbits.helpers import template_renderer
from lnbits.tasks import catch_everything_and_restart
-from starlette.staticfiles import StaticFiles
db = Database("ext_nostrclient")
diff --git a/crud.py b/crud.py
index 497fcd7..780642d 100644
--- a/crud.py
+++ b/crud.py
@@ -1,6 +1,7 @@
from typing import List, Optional, Union
import shortuuid
+
from lnbits.helpers import urlsafe_short_hash
from . import db
diff --git a/models.py b/models.py
index 75a086d..4ed1e30 100644
--- a/models.py
+++ b/models.py
@@ -3,9 +3,10 @@ from typing import Dict, List, Optional
from fastapi import Request
from fastapi.param_functions import Query
-from lnbits.helpers import urlsafe_short_hash
from pydantic import BaseModel, Field
+from lnbits.helpers import urlsafe_short_hash
+
class Relay(BaseModel):
id: Optional[str] = None
diff --git a/nostr/bech32.py b/nostr/bech32.py
index b068de7..0ae6c80 100644
--- a/nostr/bech32.py
+++ b/nostr/bech32.py
@@ -23,21 +23,25 @@
from enum import Enum
+
class Encoding(Enum):
"""Enumeration type to list the various supported encodings."""
+
BECH32 = 1
BECH32M = 2
+
CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
-BECH32M_CONST = 0x2bc830a3
+BECH32M_CONST = 0x2BC830A3
+
def bech32_polymod(values):
"""Internal function that computes the Bech32 checksum."""
- generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
+ generator = [0x3B6A57B2, 0x26508E6D, 0x1EA119FA, 0x3D4233DD, 0x2A1462B3]
chk = 1
for value in values:
top = chk >> 25
- chk = (chk & 0x1ffffff) << 5 ^ value
+ chk = (chk & 0x1FFFFFF) << 5 ^ value
for i in range(5):
chk ^= generator[i] if ((top >> i) & 1) else 0
return chk
@@ -57,6 +61,7 @@ def bech32_verify_checksum(hrp, data):
return Encoding.BECH32M
return None
+
def bech32_create_checksum(hrp, data, spec):
"""Compute the checksum values given HRP and data."""
values = bech32_hrp_expand(hrp) + data
@@ -68,26 +73,29 @@ def bech32_create_checksum(hrp, data, spec):
def bech32_encode(hrp, data, spec):
"""Compute a Bech32 string given HRP and data values."""
combined = data + bech32_create_checksum(hrp, data, spec)
- return hrp + '1' + ''.join([CHARSET[d] for d in combined])
+ return hrp + "1" + "".join([CHARSET[d] for d in combined])
+
def bech32_decode(bech):
"""Validate a Bech32/Bech32m string, and determine HRP and data."""
- if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or
- (bech.lower() != bech and bech.upper() != bech)):
+ if (any(ord(x) < 33 or ord(x) > 126 for x in bech)) or (
+ bech.lower() != bech and bech.upper() != bech
+ ):
return (None, None, None)
bech = bech.lower()
- pos = bech.rfind('1')
+ pos = bech.rfind("1")
if pos < 1 or pos + 7 > len(bech) or len(bech) > 90:
return (None, None, None)
- if not all(x in CHARSET for x in bech[pos+1:]):
+ if not all(x in CHARSET for x in bech[pos + 1 :]):
return (None, None, None)
hrp = bech[:pos]
- data = [CHARSET.find(x) for x in bech[pos+1:]]
+ data = [CHARSET.find(x) for x in bech[pos + 1 :]]
spec = bech32_verify_checksum(hrp, data)
if spec is None:
return (None, None, None)
return (hrp, data[:-6], spec)
+
def convertbits(data, frombits, tobits, pad=True):
"""General power-of-2 base conversion."""
acc = 0
@@ -123,7 +131,12 @@ def decode(hrp, addr):
return (None, None)
if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32:
return (None, None)
- if data[0] == 0 and spec != Encoding.BECH32 or data[0] != 0 and spec != Encoding.BECH32M:
+ if (
+ data[0] == 0
+ and spec != Encoding.BECH32
+ or data[0] != 0
+ and spec != Encoding.BECH32M
+ ):
return (None, None)
return (data[0], decoded)
diff --git a/nostr/client/cbc.py b/nostr/client/cbc.py
index a41dbc0..e69e8b5 100644
--- a/nostr/client/cbc.py
+++ b/nostr/client/cbc.py
@@ -1,4 +1,3 @@
-
from Cryptodome import Random
from Cryptodome.Cipher import AES
@@ -11,10 +10,10 @@ key = bytes.fromhex("3aa925cb69eb613e2928f8a18279c78b1dca04541dfd064df2eda66b598
BLOCK_SIZE = 16
-class AESCipher(object):
- """This class is compatible with crypto.createCipheriv('aes-256-cbc')
- """
+class AESCipher(object):
+ """This class is compatible with crypto.createCipheriv('aes-256-cbc')"""
+
def __init__(self, key=None):
self.key = key
@@ -33,9 +32,10 @@ class AESCipher(object):
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
+ print(dec_text)
diff --git a/nostr/client/client.py b/nostr/client/client.py
index 6fb885f..bf91050 100644
--- a/nostr/client/client.py
+++ b/nostr/client/client.py
@@ -1,19 +1,15 @@
-from typing import *
-import ssl
-import time
+import base64
import json
import os
-import base64
-
-from ..event import Event
-from ..relay_manager import RelayManager
-from ..message_type import ClientMessageType
-from ..key import PrivateKey, PublicKey
+import ssl
+import time
+from typing import *
+from ..event import EncryptedDirectMessage, Event, EventKind
from ..filter import Filter, Filters
-from ..event import Event, EventKind, EncryptedDirectMessage
-from ..relay_manager import RelayManager
+from ..key import PrivateKey, PublicKey
from ..message_type import ClientMessageType
+from ..relay_manager import RelayManager
# from aes import AESCipher
from . import cbc
diff --git a/nostr/delegation.py b/nostr/delegation.py
index 94801f5..8b1c311 100644
--- a/nostr/delegation.py
+++ b/nostr/delegation.py
@@ -7,23 +7,23 @@ class Delegation:
delegator_pubkey: str
delegatee_pubkey: str
event_kind: int
- duration_secs: int = 30*24*60 # default to 30 days
+ duration_secs: int = 30 * 24 * 60 # default to 30 days
signature: str = None # set in PrivateKey.sign_delegation
@property
def expires(self) -> int:
return int(time.time()) + self.duration_secs
-
+
@property
def conditions(self) -> str:
return f"kind={self.event_kind}&created_at<{self.expires}"
-
+
@property
def delegation_token(self) -> str:
return f"nostr:delegation:{self.delegatee_pubkey}:{self.conditions}"
def get_tag(self) -> list[str]:
- """ Called by Event """
+ """Called by Event"""
return [
"delegation",
self.delegator_pubkey,
diff --git a/nostr/event.py b/nostr/event.py
index b903e0e..65b187d 100644
--- a/nostr/event.py
+++ b/nostr/event.py
@@ -1,10 +1,11 @@
-import time
import json
+import time
from dataclasses import dataclass, field
from enum import IntEnum
-from typing import List
-from secp256k1 import PublicKey
from hashlib import sha256
+from typing import List
+
+from secp256k1 import PublicKey
from .message_type import ClientMessageType
diff --git a/nostr/key.py b/nostr/key.py
index d34697f..8089e11 100644
--- a/nostr/key.py
+++ b/nostr/key.py
@@ -1,14 +1,15 @@
-import secrets
import base64
-import secp256k1
-from cffi import FFI
-from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
-from cryptography.hazmat.primitives import padding
+import secrets
from hashlib import sha256
+import secp256k1
+from cffi import FFI
+from cryptography.hazmat.primitives import padding
+from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+
+from . import bech32
from .delegation import Delegation
from .event import EncryptedDirectMessage, Event, EventKind
-from . import bech32
class PublicKey:
diff --git a/nostr/message_pool.py b/nostr/message_pool.py
index d364cf2..373d9fc 100644
--- a/nostr/message_pool.py
+++ b/nostr/message_pool.py
@@ -1,8 +1,9 @@
import json
from queue import Queue
from threading import Lock
-from .message_type import RelayMessageType
+
from .event import Event
+from .message_type import RelayMessageType
class EventMessage:
diff --git a/nostr/message_type.py b/nostr/message_type.py
index 3f5206b..9f3be78 100644
--- a/nostr/message_type.py
+++ b/nostr/message_type.py
@@ -3,6 +3,7 @@ class ClientMessageType:
REQUEST = "REQ"
CLOSE = "CLOSE"
+
class RelayMessageType:
EVENT = "EVENT"
NOTICE = "NOTICE"
@@ -10,6 +11,10 @@ class RelayMessageType:
@staticmethod
def is_valid(type: str) -> bool:
- if type == RelayMessageType.EVENT or type == RelayMessageType.NOTICE or type == RelayMessageType.END_OF_STORED_EVENTS:
+ if (
+ type == RelayMessageType.EVENT
+ or type == RelayMessageType.NOTICE
+ or type == RelayMessageType.END_OF_STORED_EVENTS
+ ):
return True
- return False
\ No newline at end of file
+ return False
diff --git a/nostr/pow.py b/nostr/pow.py
index e006288..034ad9a 100644
--- a/nostr/pow.py
+++ b/nostr/pow.py
@@ -1,7 +1,9 @@
import time
+
from .event import Event
from .key import PrivateKey
+
def zero_bits(b: int) -> int:
n = 0
@@ -14,10 +16,11 @@ def zero_bits(b: int) -> int:
return 7 - n
+
def count_leading_zero_bits(hex_str: str) -> int:
total = 0
for i in range(0, len(hex_str) - 2, 2):
- bits = zero_bits(int(hex_str[i:i+2], 16))
+ bits = zero_bits(int(hex_str[i : i + 2], 16))
total += bits
if bits != 8:
@@ -25,7 +28,10 @@ def count_leading_zero_bits(hex_str: str) -> int:
return total
-def mine_event(content: str, difficulty: int, public_key: str, kind: int, tags: list=[]) -> Event:
+
+def mine_event(
+ content: str, difficulty: int, public_key: str, kind: int, tags: list = []
+) -> Event:
all_tags = [["nonce", "1", str(difficulty)]]
all_tags.extend(tags)
@@ -43,6 +49,7 @@ def mine_event(content: str, difficulty: int, public_key: str, kind: int, tags:
return Event(public_key, content, created_at, kind, all_tags, event_id)
+
def mine_key(difficulty: int) -> PrivateKey:
sk = PrivateKey()
num_leading_zero_bits = count_leading_zero_bits(sk.public_key.hex())
diff --git a/nostr/relay.py b/nostr/relay.py
index ee78baa..0851c02 100644
--- a/nostr/relay.py
+++ b/nostr/relay.py
@@ -2,7 +2,9 @@ import json
import time
from queue import Queue
from threading import Lock
+
from websocket import WebSocketApp
+
from .event import Event
from .filter import Filters
from .message_pool import MessagePool
diff --git a/nostr/subscription.py b/nostr/subscription.py
index 7afba20..10b5363 100644
--- a/nostr/subscription.py
+++ b/nostr/subscription.py
@@ -1,12 +1,10 @@
from .filter import Filters
+
class Subscription:
- def __init__(self, id: str, filters: Filters=None) -> None:
+ def __init__(self, id: str, filters: Filters = None) -> None:
self.id = id
self.filters = filters
def to_json_object(self):
- return {
- "id": self.id,
- "filters": self.filters.to_json_array()
- }
+ return {"id": self.id, "filters": self.filters.to_json_array()}
diff --git a/services.py b/services.py
index 09e9235..ab65dae 100644
--- a/services.py
+++ b/services.py
@@ -3,16 +3,16 @@ import json
from typing import List, Union
from fastapi import WebSocket, WebSocketDisconnect
+
from lnbits.helpers import urlsafe_short_hash
-from .nostr.client.client import NostrClient as NostrClientLib
from .models import Event, Filter, Filters, Relay, RelayList
+from .nostr.client.client import NostrClient as NostrClientLib
from .nostr.event import Event as NostrEvent
from .nostr.filter import Filter as NostrFilter
from .nostr.filter import Filters as NostrFilters
from .nostr.message_pool import EndOfStoredEventsMessage, EventMessage, NoticeMessage
-
received_subscription_events: dict[str, list[Event]] = {}
received_subscription_notices: dict[str, list[NoticeMessage]] = {}
received_subscription_eosenotices: dict[str, EndOfStoredEventsMessage] = {}
diff --git a/tasks.py b/tasks.py
index 7894e40..1566f65 100644
--- a/tasks.py
+++ b/tasks.py
@@ -2,20 +2,18 @@ import asyncio
import ssl
import threading
+from .crud import get_relays
from .nostr.event import Event
from .nostr.key import PublicKey
from .nostr.message_pool import EndOfStoredEventsMessage, EventMessage, NoticeMessage
from .nostr.relay_manager import RelayManager
from .services import (
nostr,
- received_subscription_events,
received_subscription_eosenotices,
+ received_subscription_events,
)
-from .crud import get_relays
-
-
async def init_relays():
# reinitialize the entire client
nostr.__init__()
diff --git a/templates/nostrclient/index.html b/templates/nostrclient/index.html
index 2a6cf60..b3d58c8 100644
--- a/templates/nostrclient/index.html
+++ b/templates/nostrclient/index.html
@@ -75,12 +75,17 @@
-