diff --git a/__init__.py b/__init__.py index b09d0e9..019df68 100644 --- a/__init__.py +++ b/__init__.py @@ -1,5 +1,6 @@ import asyncio from typing import List + from fastapi import APIRouter from starlette.staticfiles import StaticFiles diff --git a/helpers.py b/helpers.py new file mode 100644 index 0000000..bcf5c02 --- /dev/null +++ b/helpers.py @@ -0,0 +1,19 @@ +from bech32 import bech32_decode, convertbits + + +def normalize_public_key(pubkey: str) -> str: + if pubkey.startswith("npub1"): + _, decoded_data = bech32_decode(pubkey) + if not decoded_data: + raise ValueError("Public Key is not valid npub") + + decoded_data_bits = convertbits(decoded_data, 5, 8, False) + if not decoded_data_bits: + raise ValueError("Public Key is not valid npub") + return bytes(decoded_data_bits).hex() + + # check if valid hex + if len(pubkey) != 64: + raise ValueError("Public Key is not valid hex") + int(pubkey, 16) + return pubkey diff --git a/models.py b/models.py index 4ed1e30..1456d83 100644 --- a/models.py +++ b/models.py @@ -50,6 +50,16 @@ class Filters(BaseModel): __root__: List[Filter] +class TestMessage(BaseModel): + sender_private_key: Optional[str] + reciever_public_key: str + message: str + +class TestMessageResponse(BaseModel): + private_key: str + public_key: str + event_json: str + # class nostrKeys(BaseModel): # pubkey: str # privkey: str diff --git a/services.py b/services.py index 474bf90..82f6578 100644 --- a/services.py +++ b/services.py @@ -4,6 +4,7 @@ from typing import List, Union from fastapi import WebSocket, WebSocketDisconnect from loguru import logger + from lnbits.helpers import urlsafe_short_hash from .models import Event, Filter, Filters, Relay, RelayList diff --git a/tasks.py b/tasks.py index ab9a656..beff9db 100644 --- a/tasks.py +++ b/tasks.py @@ -1,6 +1,6 @@ import asyncio -import ssl import json +import ssl import threading from .crud import get_relays @@ -11,8 +11,8 @@ from .nostr.relay_manager import RelayManager from .services import ( nostr, received_subscription_eosenotices, - received_subscription_notices, received_subscription_events, + received_subscription_notices, ) diff --git a/templates/nostrclient/index.html b/templates/nostrclient/index.html index abe5a87..10fd4a5 100644 --- a/templates/nostrclient/index.html +++ b/templates/nostrclient/index.html @@ -2,6 +2,26 @@ %} {% block page %} {% raw %}
+ + +
+
+ +
+
+ Add relay + +
+
+
+
@@ -42,7 +62,6 @@
{{ col.label }}
- @@ -76,36 +95,167 @@ - -
- Your endpoint: - -
-
- - +
-
- -
-
- Add relay +
+
+ Copy address + Your endpoint: + +
- + + + + +
+
+ Sender Private Key: +
+
+ +
+
+
+
+
+ + + No not use your real private key! Leave empty for a randomly + generated key. + +
+
+
+
+ Sender Public Key: +
+
+ +
+
+
+
+ Test Message: +
+
+ +
+
+
+
+ Receiver Public Key: +
+
+ +
+
+
+
+
+ + This is the recipient of the message. Field required. + +
+
+
+
+ Send Message +
+
+
+ + + +
+
+ Sent Data: +
+
+ +
+
+
+
+ Received Data: +
+
+ +
+
+
+
@@ -120,7 +270,6 @@

- { + const separator = '='.repeat(80) + this.testData.receivedData = + e.data + `\n\n${separator}\n` + this.testData.receivedData + } + + this.testData.wsConnection.onmessage = updateReciveData + this.testData.wsConnection.onerror = updateReciveData + this.testData.wsConnection.onclose = updateReciveData + }, + closeWebsocket: async function () { + try { + if (this.testData.wsConnection) { + this.testData.wsConnection.close() + await this.sleep(100) + } + } catch (error) { + console.warn(error) + } + }, exportlnurldeviceCSV: function () { var self = this LNbits.utils.exportCSV(self.relayTable.columns, this.nostrLinks) - } + }, + sleep: ms => new Promise(r => setTimeout(r, ms)) }, created: function () { var self = this diff --git a/views.py b/views.py index 4214612..57b73a1 100644 --- a/views.py +++ b/views.py @@ -1,4 +1,4 @@ -from fastapi import Request, Depends +from fastapi import Depends, Request from fastapi.templating import Jinja2Templates from starlette.responses import HTMLResponse diff --git a/views_api.py b/views_api.py index c0be01e..12f4f79 100644 --- a/views_api.py +++ b/views_api.py @@ -1,4 +1,5 @@ import asyncio +import json from http import HTTPStatus from typing import Optional @@ -11,7 +12,9 @@ from lnbits.helpers import urlsafe_short_hash from . import nostrclient_ext, scheduled_tasks from .crud import add_relay, delete_relay, get_relays -from .models import Relay, RelayList +from .helpers import normalize_public_key +from .models import Relay, RelayList, TestMessage, TestMessageResponse +from .nostr.key import EncryptedDirectMessage, PrivateKey from .services import NostrRouter, nostr from .tasks import init_relays @@ -75,6 +78,36 @@ async def api_delete_relay(relay: Relay) -> None: await delete_relay(relay) +@nostrclient_ext.put( + "/api/v1/relay/test", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] +) +async def api_test_endpoint(data: TestMessage) -> TestMessageResponse: + try: + to_public_key = normalize_public_key(data.reciever_public_key) + + pk = bytes.fromhex(data.sender_private_key) if data.sender_private_key else None + private_key = PrivateKey(pk) + + dm = EncryptedDirectMessage( + recipient_pubkey=to_public_key, cleartext_content=data.message + ) + private_key.sign_event(dm) + + return TestMessageResponse(private_key=private_key.hex(), public_key=to_public_key, event_json=dm.to_message()) + except (ValueError, AssertionError) as ex: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail=str(ex), + ) + except Exception as ex: + logger.warning(ex) + raise HTTPException( + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, + detail="Cannot generate test event", + ) + + + @nostrclient_ext.delete( "/api/v1", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] )