init
This commit is contained in:
commit
69bf22c9ec
21 changed files with 778 additions and 0 deletions
3
README.md
Normal file
3
README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Nostr
|
||||
|
||||
Opens a Nostr daemon
|
||||
36
__init__.py
Normal file
36
__init__.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
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
|
||||
|
||||
db = Database("ext_nostrclient")
|
||||
|
||||
nostrclient_static_files = [
|
||||
{
|
||||
"path": "/nostrclient/static",
|
||||
"app": StaticFiles(directory="lnbits/extensions/nostrclient/static"),
|
||||
"name": "nostrclient_static",
|
||||
}
|
||||
]
|
||||
|
||||
nostrclient_ext: APIRouter = APIRouter(prefix="/nostrclient", tags=["nostrclient"])
|
||||
|
||||
|
||||
def nostr_renderer():
|
||||
return template_renderer(["lnbits/extensions/nostrclient/templates"])
|
||||
|
||||
|
||||
from .views import * # noqa
|
||||
from .views_api import * # noqa
|
||||
|
||||
from .tasks import init_relays, subscribe_events
|
||||
|
||||
|
||||
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))
|
||||
BIN
__pycache__/__init__.cpython-39.pyc
Normal file
BIN
__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
__pycache__/crud.cpython-39.pyc
Normal file
BIN
__pycache__/crud.cpython-39.pyc
Normal file
Binary file not shown.
BIN
__pycache__/migrations.cpython-39.pyc
Normal file
BIN
__pycache__/migrations.cpython-39.pyc
Normal file
Binary file not shown.
BIN
__pycache__/models.cpython-39.pyc
Normal file
BIN
__pycache__/models.cpython-39.pyc
Normal file
Binary file not shown.
BIN
__pycache__/tasks.cpython-39.pyc
Normal file
BIN
__pycache__/tasks.cpython-39.pyc
Normal file
Binary file not shown.
BIN
__pycache__/views.cpython-39.pyc
Normal file
BIN
__pycache__/views.cpython-39.pyc
Normal file
Binary file not shown.
BIN
__pycache__/views_api.cpython-39.pyc
Normal file
BIN
__pycache__/views_api.cpython-39.pyc
Normal file
Binary file not shown.
26
cbc.py
Normal file
26
cbc.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
from Cryptodome.Cipher import AES
|
||||
|
||||
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"))
|
||||
6
config.json
Normal file
6
config.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "Nostr Client",
|
||||
"short_description": "Nostr client for extensions",
|
||||
"tile": "/nostr-client/static/images/nostr-bitcoin.png",
|
||||
"contributors": ["calle"]
|
||||
}
|
||||
29
crud.py
Normal file
29
crud.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
from typing import List, Optional, Union
|
||||
|
||||
from lnbits.helpers import urlsafe_short_hash
|
||||
import shortuuid
|
||||
from . import db
|
||||
from .models import Relay, RelayList
|
||||
|
||||
|
||||
async def get_relays() -> RelayList:
|
||||
row = await db.fetchall("SELECT * FROM nostradmin.relays")
|
||||
return RelayList(__root__=row)
|
||||
|
||||
|
||||
async def add_relay(relay: Relay) -> None:
|
||||
await db.execute(
|
||||
f"""
|
||||
INSERT INTO nostradmin.relays (
|
||||
id,
|
||||
url,
|
||||
active
|
||||
)
|
||||
VALUES (?, ?, ?)
|
||||
""",
|
||||
(relay.id, relay.url, relay.active),
|
||||
)
|
||||
|
||||
|
||||
async def delete_relay(relay: Relay) -> None:
|
||||
await db.execute("DELETE FROM nostradmin.relays WHERE id = ?", (relay.id,))
|
||||
9
manifest.json
Normal file
9
manifest.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"repos": [
|
||||
{
|
||||
"id": "nostr-client",
|
||||
"organisation": "lnbits",
|
||||
"repository": "nostr-client-extension"
|
||||
}
|
||||
]
|
||||
}
|
||||
13
migrations.py
Normal file
13
migrations.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
async def m001_initial(db):
|
||||
"""
|
||||
Initial nostrclient table.
|
||||
"""
|
||||
await db.execute(
|
||||
f"""
|
||||
CREATE TABLE nostrclient.relays (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
url TEXT NOT NULL,
|
||||
active BOOLEAN DEFAULT true
|
||||
);
|
||||
"""
|
||||
)
|
||||
92
models.py
Normal file
92
models.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
from typing import List, Dict
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import Request
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from fastapi.param_functions import Query
|
||||
from dataclasses import dataclass
|
||||
from lnbits.helpers import urlsafe_short_hash
|
||||
|
||||
|
||||
class Relay(BaseModel):
|
||||
id: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
connected: Optional[bool] = None
|
||||
connected_string: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
active: Optional[bool] = None
|
||||
ping: Optional[int] = None
|
||||
|
||||
def _init__(self):
|
||||
if not self.id:
|
||||
self.id = urlsafe_short_hash()
|
||||
|
||||
|
||||
class RelayList(BaseModel):
|
||||
__root__: List[Relay]
|
||||
|
||||
|
||||
class Event(BaseModel):
|
||||
content: str
|
||||
pubkey: str
|
||||
created_at: Optional[int]
|
||||
kind: int
|
||||
tags: Optional[List[List[str]]]
|
||||
sig: str
|
||||
|
||||
|
||||
class Filter(BaseModel):
|
||||
ids: Optional[List[str]]
|
||||
kinds: Optional[List[int]]
|
||||
authors: Optional[List[str]]
|
||||
since: Optional[int]
|
||||
until: Optional[int]
|
||||
e: Optional[List[str]] = Field(alias="#e")
|
||||
p: Optional[List[str]] = Field(alias="#p")
|
||||
limit: Optional[int]
|
||||
|
||||
|
||||
class Filters(BaseModel):
|
||||
__root__: List[Filter]
|
||||
|
||||
|
||||
# class nostrKeys(BaseModel):
|
||||
# pubkey: str
|
||||
# privkey: str
|
||||
|
||||
# class nostrNotes(BaseModel):
|
||||
# id: str
|
||||
# pubkey: str
|
||||
# created_at: str
|
||||
# kind: int
|
||||
# tags: str
|
||||
# content: str
|
||||
# sig: str
|
||||
|
||||
# class nostrCreateRelays(BaseModel):
|
||||
# relay: str = Query(None)
|
||||
|
||||
# class nostrCreateConnections(BaseModel):
|
||||
# pubkey: str = Query(None)
|
||||
# relayid: str = Query(None)
|
||||
|
||||
# class nostrRelays(BaseModel):
|
||||
# id: Optional[str]
|
||||
# relay: Optional[str]
|
||||
# status: Optional[bool] = False
|
||||
|
||||
|
||||
# class nostrRelaySetList(BaseModel):
|
||||
# allowlist: Optional[str]
|
||||
# denylist: Optional[str]
|
||||
|
||||
# class nostrConnections(BaseModel):
|
||||
# id: str
|
||||
# pubkey: Optional[str]
|
||||
# relayid: Optional[str]
|
||||
|
||||
# class nostrSubscriptions(BaseModel):
|
||||
# id: str
|
||||
# userPubkey: Optional[str]
|
||||
# subscribedPubkey: Optional[str]
|
||||
1
nostr
Submodule
1
nostr
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit f598039e440f1d57c3b5d993ff44473649ffac3d
|
||||
BIN
static/images/nostr-bitcoin.png
Normal file
BIN
static/images/nostr-bitcoin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
88
tasks.py
Normal file
88
tasks.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import asyncio
|
||||
import ssl
|
||||
import threading
|
||||
|
||||
from .nostr.nostr.client.client import NostrClient
|
||||
from .nostr.nostr.event import Event
|
||||
from .nostr.nostr.key import PublicKey
|
||||
from .nostr.nostr.relay_manager import RelayManager
|
||||
|
||||
# relays = [
|
||||
# "wss://nostr.mom",
|
||||
# "wss://nostr-pub.wellorder.net",
|
||||
# "wss://nostr.zebedee.cloud",
|
||||
# "wss://relay.damus.io",
|
||||
# "wss://relay.nostr.info",
|
||||
# "wss://nostr.onsats.org",
|
||||
# "wss://nostr-relay.untethr.me",
|
||||
# "wss://relay.snort.social",
|
||||
# "wss://lnbits.link/nostrrelay/client",
|
||||
# ]
|
||||
client = NostrClient(
|
||||
connect=False,
|
||||
)
|
||||
|
||||
# client = NostrClient(
|
||||
# connect=False,
|
||||
# privatekey_hex="211aac75a687ad96cca402406f8147a2726e31c5fc838e22ce30640ca1f3a6fe",
|
||||
# )
|
||||
|
||||
received_event_queue: asyncio.Queue[Event] = asyncio.Queue(0)
|
||||
|
||||
from .crud import get_relays
|
||||
|
||||
|
||||
async def init_relays():
|
||||
relays = await get_relays()
|
||||
client.relays = [r.url for r in relays.__root__]
|
||||
client.connect()
|
||||
return
|
||||
|
||||
|
||||
# async def send_data():
|
||||
# while not any([r.connected for r in client.relay_manager.relays.values()]):
|
||||
# print("no relays connected yet")
|
||||
# await asyncio.sleep(0.5)
|
||||
# while True:
|
||||
# client.dm("test", PublicKey(bytes.fromhex(client.public_key.hex())))
|
||||
# print("sent DM")
|
||||
# await asyncio.sleep(5)
|
||||
# return
|
||||
|
||||
|
||||
# async def receive_data():
|
||||
# while not any([r.connected for r in client.relay_manager.relays.values()]):
|
||||
# print("no relays connected yet")
|
||||
# await asyncio.sleep(0.5)
|
||||
|
||||
# def callback(event: Event, decrypted_content=None):
|
||||
# print(
|
||||
# f"From {event.public_key[:3]}..{event.public_key[-3:]}: {decrypted_content or event.content}"
|
||||
# )
|
||||
|
||||
# t = threading.Thread(
|
||||
# target=client.get_dm,
|
||||
# args=(
|
||||
# client.public_key,
|
||||
# callback,
|
||||
# ),
|
||||
# name="Nostr DM",
|
||||
# )
|
||||
# t.start()
|
||||
|
||||
|
||||
async def subscribe_events():
|
||||
while not any([r.connected for r in client.relay_manager.relays.values()]):
|
||||
print("no relays connected yet")
|
||||
await asyncio.sleep(1)
|
||||
|
||||
def callback(event: Event):
|
||||
print(f"From {event.public_key[:3]}..{event.public_key[-3:]}: {event.content}")
|
||||
asyncio.run(received_event_queue.put(event))
|
||||
|
||||
t = threading.Thread(
|
||||
target=client.subscribe,
|
||||
args=(callback,),
|
||||
name="Nostr-event-subscription",
|
||||
)
|
||||
t.start()
|
||||
257
templates/nostr-client/index.html
Normal file
257
templates/nostr-client/index.html
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
||||
%} {% block page %}
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-7 q-gutter-y-md">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="row items-center no-wrap q-mb-md">
|
||||
<div class="col">
|
||||
<h5 class="text-subtitle1 q-my-none">nostr relays</h5>
|
||||
</div>
|
||||
|
||||
<div class="col-auto">
|
||||
<q-input
|
||||
borderless
|
||||
dense
|
||||
debounce="300"
|
||||
v-model="filter"
|
||||
placeholder="Search"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<q-icon name="search"></q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</div>
|
||||
<q-table
|
||||
flat
|
||||
dense
|
||||
:data="nostrrelayLinks"
|
||||
row-key="id"
|
||||
:columns="relayTable.columns"
|
||||
:pagination.sync="relayTable.pagination"
|
||||
:filter="filter"
|
||||
>
|
||||
{% raw %}
|
||||
<template v-slot:header="props">
|
||||
<q-tr :props="props">
|
||||
<q-th
|
||||
v-for="col in props.cols"
|
||||
:key="col.name"
|
||||
:props="props"
|
||||
auto-width
|
||||
>
|
||||
<div v-if="col.name == 'id'"></div>
|
||||
<div v-else>{{ col.label }}</div>
|
||||
</q-th>
|
||||
<!-- <q-th auto-width></q-th> -->
|
||||
</q-tr>
|
||||
</template>
|
||||
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props">
|
||||
<q-td
|
||||
v-for="col in props.cols"
|
||||
:key="col.name"
|
||||
:props="props"
|
||||
auto-width
|
||||
>
|
||||
<div v-if="col.name == 'id'"></div>
|
||||
<div v-else>
|
||||
<div v-if="col.value == true" style="background-color: green">
|
||||
{{ col.value }}
|
||||
</div>
|
||||
<div v-else>{{ col.value }}</div>
|
||||
</div>
|
||||
</q-td>
|
||||
<q-td auto-width>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
size="xs"
|
||||
@click="deleteRelay(props.row.id)"
|
||||
icon="cancel"
|
||||
color="pink"
|
||||
></q-btn>
|
||||
</q-tr>
|
||||
</template>
|
||||
{% endraw %}
|
||||
</q-table>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<q-card>
|
||||
<!-- <q-tabs
|
||||
v-model="listSelection"
|
||||
dense
|
||||
class="text-grey"
|
||||
active-color="primary"
|
||||
indicator-color="primary"
|
||||
align="justify"
|
||||
narrow-indicator
|
||||
>
|
||||
<q-tab name="denylist" label="Deny List"></q-tab>
|
||||
<q-tab name="allowlist" label="Allow List"></q-tab>
|
||||
</q-tabs> -->
|
||||
<q-separator></q-separator>
|
||||
<q-form class="q-gutter-md q-y-md" @submit="addRelay">
|
||||
<div class="row">
|
||||
<div class="col q-mx-lg q-my-sm">
|
||||
<q-input v-model="relayToAdd" dense filled autogrow></q-input>
|
||||
</div>
|
||||
<div class="col q-mx-lg items-align flex items-center justify-right">
|
||||
<q-btn unelevated color="primary" type="submit"> Add relay </q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-5 q-gutter-y-md">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<h6 class="text-subtitle1 q-my-none">{{SITE_TITLE}} Nostr Extension</h6>
|
||||
<p>Only Admin users can manage this extension</p>
|
||||
|
||||
<q-card-section></q-card-section>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||
|
||||
<script>
|
||||
Vue.component(VueQrcode.name, VueQrcode)
|
||||
|
||||
var maplrelays = obj => {
|
||||
obj._data = _.clone(obj)
|
||||
obj.theTime = obj.time * 60 - (Date.now() / 1000 - obj.timestamp)
|
||||
obj.time = obj.time + 'mins'
|
||||
|
||||
obj.ping = obj.ping + ' ms'
|
||||
|
||||
if (obj.time_elapsed) {
|
||||
obj.date = 'Time elapsed'
|
||||
} else {
|
||||
obj.date = Quasar.utils.date.formatDate(
|
||||
new Date((obj.theTime - 3600) * 1000),
|
||||
'HH:mm:ss'
|
||||
)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
new Vue({
|
||||
el: '#vue',
|
||||
mixins: [windowMixin],
|
||||
data: function () {
|
||||
return {
|
||||
relayToAdd: '',
|
||||
nostrrelayLinks: [],
|
||||
filter: '',
|
||||
relayTable: {
|
||||
columns: [
|
||||
{
|
||||
name: 'connected_string',
|
||||
align: 'left',
|
||||
label: '',
|
||||
field: 'connected_string'
|
||||
},
|
||||
{
|
||||
name: 'relay',
|
||||
align: 'left',
|
||||
label: 'URL',
|
||||
field: 'url'
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
align: 'center',
|
||||
label: 'Status',
|
||||
field: 'status'
|
||||
},
|
||||
{
|
||||
name: 'ping',
|
||||
align: 'center',
|
||||
label: 'Ping',
|
||||
field: 'ping'
|
||||
}
|
||||
],
|
||||
pagination: {
|
||||
rowsPerPage: 10
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getRelays: function () {
|
||||
var self = this
|
||||
LNbits.api
|
||||
.request(
|
||||
'GET',
|
||||
'/nostradmin/api/v1/relays',
|
||||
self.g.user.wallets[0].adminkey
|
||||
)
|
||||
.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) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
},
|
||||
addRelay: function () {
|
||||
console.log("ADD RELAY " + this.relayToAdd)
|
||||
var self = this
|
||||
LNbits.api
|
||||
.request(
|
||||
'POST',
|
||||
'/nostradmin/api/v1/relay',
|
||||
self.g.user.wallets[0].adminkey,
|
||||
{url:this.relayToAdd},
|
||||
)
|
||||
.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) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
},
|
||||
deleteRelay: function (relay_id) {
|
||||
console.log("DELETE RELAY " + relay_id)
|
||||
var self = this
|
||||
LNbits.api
|
||||
.request(
|
||||
'DELETE',
|
||||
'/nostradmin/api/v1/relay',
|
||||
self.g.user.wallets[0].adminkey,
|
||||
{id:relay_id},
|
||||
)
|
||||
.then(function (response) {
|
||||
if (response.data) {
|
||||
console.log(response.data)
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
},
|
||||
exportlnurldeviceCSV: function () {
|
||||
var self = this
|
||||
LNbits.utils.exportCSV(self.relayTable.columns, this.nostrLinks)
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
var self = this
|
||||
this.getRelays()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
100
views.py
Normal file
100
views.py
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
from http import HTTPStatus
|
||||
import asyncio
|
||||
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
|
||||
|
||||
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
|
||||
@nostrclient_ext.get("/", response_class=HTMLResponse)
|
||||
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
|
||||
118
views_api.py
Normal file
118
views_api.py
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
from http import HTTPStatus
|
||||
import asyncio
|
||||
import ssl
|
||||
import json
|
||||
from fastapi import Request
|
||||
from fastapi.param_functions import Query
|
||||
from fastapi.params import Depends
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from starlette.exceptions import HTTPException
|
||||
from sse_starlette.sse import EventSourceResponse
|
||||
|
||||
from . import nostrclient_ext
|
||||
|
||||
from .tasks import client, received_event_queue
|
||||
|
||||
from .crud import get_relays, add_relay, delete_relay
|
||||
from .models import RelayList, Relay, Event, Filter, Filters
|
||||
|
||||
from .nostr.nostr.event import Event as NostrEvent
|
||||
from .nostr.nostr.event import EncryptedDirectMessage
|
||||
from .nostr.nostr.filter import Filter as NostrFilter
|
||||
from .nostr.nostr.filter import Filters as NostrFilters
|
||||
from .nostr.nostr.message_type import ClientMessageType
|
||||
|
||||
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
|
||||
|
||||
|
||||
@nostrclient_ext.get("/api/v1/relays")
|
||||
async def api_get_relays(): # type: ignore
|
||||
relays = RelayList(__root__=[])
|
||||
for url, r in client.relay_manager.relays.items():
|
||||
status_text = (
|
||||
f"⬆️ {r.num_sent_events} ⬇️ {r.num_received_events} ⚠️ {r.error_counter}"
|
||||
)
|
||||
connected_text = "🟢" if r.connected else "🔴"
|
||||
relay_id = urlsafe_short_hash()
|
||||
relays.__root__.append(
|
||||
Relay(
|
||||
id=relay_id,
|
||||
url=url,
|
||||
connected_string=connected_text,
|
||||
status=status_text,
|
||||
ping=r.ping,
|
||||
connected=True,
|
||||
active=True,
|
||||
)
|
||||
)
|
||||
return relays
|
||||
|
||||
|
||||
@nostrclient_ext.post("/api/v1/relay")
|
||||
async def api_add_relay(relay: Relay): # type: ignore
|
||||
assert relay.url, "no URL"
|
||||
relay.id = urlsafe_short_hash()
|
||||
await add_relay(relay)
|
||||
await init_relays()
|
||||
|
||||
|
||||
@nostrclient_ext.delete("/api/v1/relay")
|
||||
async def api_delete_relay(relay: Relay): # type: ignore
|
||||
await delete_relay(relay)
|
||||
|
||||
|
||||
@nostrclient_ext.post("/api/v1/publish")
|
||||
async def api_post_event(event: Event):
|
||||
nostr_event = NostrEvent(
|
||||
content=event.content,
|
||||
public_key=event.pubkey,
|
||||
created_at=event.created_at, # type: ignore
|
||||
kind=event.kind,
|
||||
tags=event.tags or None, # type: ignore
|
||||
signature=event.sig,
|
||||
)
|
||||
client.relay_manager.publish_event(nostr_event)
|
||||
|
||||
|
||||
@nostrclient_ext.post("/api/v1/filter")
|
||||
async def api_subscribe(filter: Filter):
|
||||
nostr_filter = NostrFilter(
|
||||
event_ids=filter.ids,
|
||||
kinds=filter.kinds, # type: ignore
|
||||
authors=filter.authors,
|
||||
since=filter.since,
|
||||
until=filter.until,
|
||||
event_refs=filter.e,
|
||||
pubkey_refs=filter.p,
|
||||
limit=filter.limit,
|
||||
)
|
||||
|
||||
filters = NostrFilters([nostr_filter])
|
||||
subscription_id = urlsafe_short_hash()
|
||||
client.relay_manager.add_subscription(subscription_id, filters)
|
||||
|
||||
request = [ClientMessageType.REQUEST, subscription_id]
|
||||
request.extend(filters.to_json_array())
|
||||
message = json.dumps(request)
|
||||
client.relay_manager.publish_message(message)
|
||||
|
||||
async def event_getter():
|
||||
while True:
|
||||
event = await received_event_queue.get()
|
||||
if filters.match(event):
|
||||
yield event.to_message()
|
||||
|
||||
return EventSourceResponse(
|
||||
event_getter(),
|
||||
ping=20,
|
||||
media_type="text/event-stream",
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue