remove nostradmin from this branch
This commit is contained in:
parent
627914abda
commit
e76c23b096
11 changed files with 0 additions and 14459 deletions
13560
coverage.xml
13560
coverage.xml
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +0,0 @@
|
||||||
# Nostr
|
|
||||||
|
|
||||||
Opens a Nostr daemon
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
from fastapi import APIRouter, Request
|
|
||||||
|
|
||||||
from lnbits.db import Database
|
|
||||||
from lnbits.helpers import template_renderer
|
|
||||||
|
|
||||||
from starlette.exceptions import HTTPException
|
|
||||||
from starlette.responses import HTMLResponse
|
|
||||||
from http import HTTPStatus
|
|
||||||
|
|
||||||
from lnbits.settings import LNBITS_ADMIN_EXTENSIONS
|
|
||||||
|
|
||||||
nostradmin_ext: APIRouter = APIRouter(prefix="/nostradmin", tags=["nostradmin"])
|
|
||||||
|
|
||||||
db = Database("ext_nostradmin")
|
|
||||||
if 'nostradmin' not in LNBITS_ADMIN_EXTENSIONS:
|
|
||||||
@nostradmin_ext.get("/", response_class=HTMLResponse)
|
|
||||||
async def index(request: Request):
|
|
||||||
return template_renderer().TemplateResponse(
|
|
||||||
"error.html", {"request": request, "err": "NostrAdmin must be added to LNBITS_ADMIN_EXTENSIONS in .env"}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
|
|
||||||
def nostr_renderer():
|
|
||||||
return template_renderer(["lnbits/extensions/nostradmin/templates"])
|
|
||||||
|
|
||||||
from .views import * # noqa
|
|
||||||
from .views_api import * # noqa
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"name": "NostrAdmin",
|
|
||||||
"short_description": "Admin daemon for Nostr",
|
|
||||||
"icon": "swap_horizontal_circle",
|
|
||||||
"contributors": ["arcbtc"]
|
|
||||||
}
|
|
||||||
|
|
@ -1,146 +0,0 @@
|
||||||
from typing import List, Optional, Union
|
|
||||||
|
|
||||||
from lnbits.helpers import urlsafe_short_hash
|
|
||||||
import shortuuid
|
|
||||||
from . import db
|
|
||||||
from .models import (
|
|
||||||
nostrKeys,
|
|
||||||
nostrNotes,
|
|
||||||
nostrCreateRelays,
|
|
||||||
nostrRelays,
|
|
||||||
nostrConnections,
|
|
||||||
nostrCreateConnections,
|
|
||||||
nostrRelayList,
|
|
||||||
)
|
|
||||||
from .models import nostrKeys, nostrCreateRelays, nostrRelaySetList
|
|
||||||
|
|
||||||
###############KEYS##################
|
|
||||||
|
|
||||||
|
|
||||||
async def create_nostrkeys(data: nostrKeys) -> nostrKeys:
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO nostradmin.keys (
|
|
||||||
pubkey,
|
|
||||||
privkey
|
|
||||||
)
|
|
||||||
VALUES (?, ?)
|
|
||||||
""",
|
|
||||||
(data.pubkey, data.privkey),
|
|
||||||
)
|
|
||||||
return await get_nostrkeys(nostrkey_id)
|
|
||||||
|
|
||||||
|
|
||||||
async def get_nostrkeys(pubkey: str) -> nostrKeys:
|
|
||||||
row = await db.fetchone("SELECT * FROM nostradmin.keys WHERE pubkey = ?", (pubkey,))
|
|
||||||
return nostrKeys(**row) if row else None
|
|
||||||
|
|
||||||
|
|
||||||
###############NOTES##################
|
|
||||||
|
|
||||||
|
|
||||||
async def create_nostrnotes(data: nostrNotes) -> nostrNotes:
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO nostradmin.notes (
|
|
||||||
id,
|
|
||||||
pubkey,
|
|
||||||
created_at,
|
|
||||||
kind,
|
|
||||||
tags,
|
|
||||||
content,
|
|
||||||
sig
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
data.id,
|
|
||||||
data.pubkey,
|
|
||||||
data.created_at,
|
|
||||||
data.kind,
|
|
||||||
data.tags,
|
|
||||||
data.content,
|
|
||||||
data.sig,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return await get_nostrnotes(data.id)
|
|
||||||
|
|
||||||
|
|
||||||
async def get_nostrnotes(nostrnote_id: str) -> nostrNotes:
|
|
||||||
row = await db.fetchone("SELECT * FROM nostradmin.notes WHERE id = ?", (nostrnote_id,))
|
|
||||||
return nostrNotes(**row) if row else None
|
|
||||||
|
|
||||||
|
|
||||||
###############RELAYS##################
|
|
||||||
|
|
||||||
|
|
||||||
async def create_nostrrelays(data: nostrCreateRelays) -> nostrCreateRelays:
|
|
||||||
nostrrelay_id = shortuuid.uuid(name=data.relay)
|
|
||||||
|
|
||||||
if await get_nostrrelay(nostrrelay_id):
|
|
||||||
return "error"
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO nostradmin.relays (
|
|
||||||
id,
|
|
||||||
relay
|
|
||||||
)
|
|
||||||
VALUES (?, ?)
|
|
||||||
""",
|
|
||||||
(nostrrelay_id, data.relay),
|
|
||||||
)
|
|
||||||
return await get_nostrrelay(nostrrelay_id)
|
|
||||||
|
|
||||||
|
|
||||||
async def get_nostrrelays() -> nostrRelays:
|
|
||||||
rows = await db.fetchall("SELECT * FROM nostradmin.relays")
|
|
||||||
return [nostrRelays(**row) for row in rows]
|
|
||||||
|
|
||||||
|
|
||||||
async def get_nostrrelay(nostrrelay_id: str) -> nostrRelays:
|
|
||||||
row = await db.fetchone("SELECT * FROM nostradmin.relays WHERE id = ?", (nostrrelay_id,))
|
|
||||||
return nostrRelays(**row) if row else None
|
|
||||||
|
|
||||||
async def update_nostrrelaysetlist(data: nostrRelaySetList) -> nostrRelayList:
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
UPDATE nostradmin.relaylists SET
|
|
||||||
denylist = ?,
|
|
||||||
allowlist = ?
|
|
||||||
WHERE id = ?
|
|
||||||
""",
|
|
||||||
(data.denylist, data.allowlist, 1),
|
|
||||||
)
|
|
||||||
return await get_nostrrelaylist()
|
|
||||||
|
|
||||||
async def get_nostrrelaylist() -> nostrRelayList:
|
|
||||||
row = await db.fetchone("SELECT * FROM nostradmin.relaylists WHERE id = ?", (1,))
|
|
||||||
return nostrRelayList(**row) if row else None
|
|
||||||
|
|
||||||
|
|
||||||
###############CONNECTIONS##################
|
|
||||||
|
|
||||||
|
|
||||||
async def create_nostrconnections(
|
|
||||||
data: nostrCreateConnections
|
|
||||||
) -> nostrCreateConnections:
|
|
||||||
nostrrelay_id = shortuuid.uuid(name=data.relayid + data.pubkey)
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO nostradmin.connections (
|
|
||||||
id,
|
|
||||||
pubkey,
|
|
||||||
relayid
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?)
|
|
||||||
""",
|
|
||||||
(data.id, data.pubkey, data.relayid),
|
|
||||||
)
|
|
||||||
return await get_nostrconnections(data.id)
|
|
||||||
|
|
||||||
|
|
||||||
async def get_nostrconnections(nostrconnections_id: str) -> nostrConnections:
|
|
||||||
row = await db.fetchone(
|
|
||||||
"SELECT * FROM nostradmin.connections WHERE id = ?", (nostrconnections_id,)
|
|
||||||
)
|
|
||||||
return nostrConnections(**row) if row else None
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
from lnbits.db import Database
|
|
||||||
|
|
||||||
async def m001_initial(db):
|
|
||||||
"""
|
|
||||||
Initial nostradmin table.
|
|
||||||
"""
|
|
||||||
await db.execute(
|
|
||||||
f"""
|
|
||||||
CREATE TABLE nostradmin.keys (
|
|
||||||
pubkey TEXT NOT NULL PRIMARY KEY,
|
|
||||||
privkey TEXT NOT NULL
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
await db.execute(
|
|
||||||
f"""
|
|
||||||
CREATE TABLE nostradmin.notes (
|
|
||||||
id TEXT NOT NULL PRIMARY KEY,
|
|
||||||
pubkey TEXT NOT NULL,
|
|
||||||
created_at TEXT NOT NULL,
|
|
||||||
kind INT NOT NULL,
|
|
||||||
tags TEXT NOT NULL,
|
|
||||||
content TEXT NOT NULL,
|
|
||||||
sig TEXT NOT NULL
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
await db.execute(
|
|
||||||
f"""
|
|
||||||
CREATE TABLE nostradmin.relays (
|
|
||||||
id TEXT NOT NULL PRIMARY KEY,
|
|
||||||
relay TEXT NOT NULL
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
await db.execute(
|
|
||||||
f"""
|
|
||||||
CREATE TABLE nostradmin.relaylists (
|
|
||||||
id TEXT NOT NULL PRIMARY KEY DEFAULT 1,
|
|
||||||
allowlist TEXT,
|
|
||||||
denylist TEXT
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO nostradmin.relaylists (
|
|
||||||
id,
|
|
||||||
denylist
|
|
||||||
)
|
|
||||||
VALUES (?, ?)
|
|
||||||
""",
|
|
||||||
("1", "wss://zucks-meta-relay.com\nwss://nostr.cia.gov",),
|
|
||||||
)
|
|
||||||
|
|
||||||
await db.execute(
|
|
||||||
f"""
|
|
||||||
CREATE TABLE nostradmin.connections (
|
|
||||||
id TEXT NOT NULL PRIMARY KEY,
|
|
||||||
publickey TEXT NOT NULL,
|
|
||||||
relayid TEXT NOT NULL
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
await db.execute(
|
|
||||||
f"""
|
|
||||||
CREATE TABLE nostradmin.subscribed (
|
|
||||||
id TEXT NOT NULL PRIMARY KEY,
|
|
||||||
userPubkey TEXT NOT NULL,
|
|
||||||
subscribedPubkey TEXT NOT NULL
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
import json
|
|
||||||
from sqlite3 import Row
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import Request
|
|
||||||
from pydantic import BaseModel
|
|
||||||
from pydantic.main import BaseModel
|
|
||||||
from fastapi.param_functions import Query
|
|
||||||
|
|
||||||
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 nostrRelayList(BaseModel):
|
|
||||||
id: str
|
|
||||||
allowlist: Optional[str]
|
|
||||||
denylist: Optional[str]
|
|
||||||
|
|
||||||
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,105 +0,0 @@
|
||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
from typing import Any, Dict
|
|
||||||
|
|
||||||
import websockets
|
|
||||||
|
|
||||||
|
|
||||||
# Many thanks to Fusion44 for https://gist.github.com/fusion44/9af0b054b4609012752b0c8c1dafbd4a
|
|
||||||
|
|
||||||
class Relay:
|
|
||||||
url: str
|
|
||||||
read: bool
|
|
||||||
write: bool
|
|
||||||
active: bool
|
|
||||||
_connection: Any
|
|
||||||
|
|
||||||
def __init__(self, url: str, read: bool, write: bool, active: bool) -> None:
|
|
||||||
self.url = url
|
|
||||||
self.read = read
|
|
||||||
self.write = write
|
|
||||||
self.active = active
|
|
||||||
|
|
||||||
async def connect(self):
|
|
||||||
self._connection = await websockets.connect(self.url)
|
|
||||||
|
|
||||||
async def send(self, message):
|
|
||||||
if not self.write or not self.active:
|
|
||||||
raise RuntimeError("Can't send to a relay that is inactive or not writable")
|
|
||||||
await self._connection.send(message)
|
|
||||||
|
|
||||||
async def listen(self):
|
|
||||||
if not self.read or not self.active:
|
|
||||||
raise RuntimeError(
|
|
||||||
"Can't listen to a relay that is inactive or not readable"
|
|
||||||
)
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
msg = await self._connection.recv()
|
|
||||||
yield msg
|
|
||||||
except websockets.ConnectionClosedOK:
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
class RelayManager:
|
|
||||||
_relays: Dict[str, Relay] = {}
|
|
||||||
_tasks: Dict[str, asyncio.Task] = {}
|
|
||||||
msg_channel = asyncio.Queue()
|
|
||||||
|
|
||||||
def __init__(self, enable_ws_debugger=False) -> None:
|
|
||||||
if enable_ws_debugger:
|
|
||||||
logger = logging.getLogger("websockets")
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
logger.addHandler(logging.StreamHandler())
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def add_relay(self, relay: Relay):
|
|
||||||
if relay.url in self._relays.keys():
|
|
||||||
raise RuntimeError(f"Relay {relay.url} exists")
|
|
||||||
|
|
||||||
if relay.active and relay.read:
|
|
||||||
await relay.connect()
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
t = loop.create_task(self._relay_listener(relay, self.msg_channel))
|
|
||||||
self._tasks[relay.url] = t
|
|
||||||
|
|
||||||
self._relays[relay.url] = relay
|
|
||||||
|
|
||||||
async def remove_relay(self, url: str):
|
|
||||||
if url in self._relays.keys():
|
|
||||||
relay = self._relays[url]
|
|
||||||
if relay.active:
|
|
||||||
t = self._tasks[url]
|
|
||||||
t.cancel()
|
|
||||||
try:
|
|
||||||
await t
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
print(f"Canceled task {relay.url}")
|
|
||||||
|
|
||||||
def get_relay(self, url: str) -> Relay:
|
|
||||||
if not url in self._relays.keys():
|
|
||||||
raise RuntimeError("Relay not found")
|
|
||||||
return self._relays.get(url)
|
|
||||||
|
|
||||||
async def update_relay(self, relay: Relay):
|
|
||||||
if relay.url in self._relays.keys():
|
|
||||||
# remove the old relay
|
|
||||||
await self.remove_relay(relay.url)
|
|
||||||
|
|
||||||
# add the new relay
|
|
||||||
await self.add_relay(relay)
|
|
||||||
else:
|
|
||||||
raise RuntimeError("Unknown Relay")
|
|
||||||
|
|
||||||
async def send_to_all(self, message):
|
|
||||||
for r in self._relays.values():
|
|
||||||
if r.write:
|
|
||||||
await r.send(message=message)
|
|
||||||
|
|
||||||
async def _relay_listener(self, relay: Relay, msg_chan: asyncio.Queue):
|
|
||||||
print(f"listening for {relay.url}")
|
|
||||||
async for msg in relay.listen():
|
|
||||||
print(msg)
|
|
||||||
msg_chan.put_nowait(msg)
|
|
||||||
print(f"STOP listening for {relay.url}")
|
|
||||||
|
|
@ -1,303 +0,0 @@
|
||||||
{% 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 ONLINE</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>
|
|
||||||
<q-btn flat color="grey" @click="exportlnurldeviceCSV"
|
|
||||||
>Export to CSV</q-btn
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<q-table
|
|
||||||
flat
|
|
||||||
dense
|
|
||||||
:data="nostrrelayLinks"
|
|
||||||
row-key="id"
|
|
||||||
:columns="nostrTable.columns"
|
|
||||||
:pagination.sync="nostrTable.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-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-tab-panels v-model="listSelection" animated>
|
|
||||||
<q-tab-panel name="denylist">
|
|
||||||
<div class="text-h6">Relays in this list will NOT be used</div>
|
|
||||||
<q-form class="q-gutter-md q-y-md" @submit="setRelayList">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col q-mx-lg">
|
|
||||||
<q-input
|
|
||||||
v-model="setList.denylist"
|
|
||||||
dense
|
|
||||||
filled
|
|
||||||
autogrow
|
|
||||||
></q-input>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="col q-mx-lg items-align flex items-center justify-center"
|
|
||||||
>
|
|
||||||
<q-btn unelevated color="primary" type="submit">
|
|
||||||
Update Deny List
|
|
||||||
</q-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-form>
|
|
||||||
</q-tab-panel>
|
|
||||||
|
|
||||||
<q-tab-panel name="allowlist">
|
|
||||||
<div class="text-h6">ONLY relays in this list will be used</div>
|
|
||||||
<q-form class="q-gutter-md q-y-md" @submit="setRelayList">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col q-mx-lg">
|
|
||||||
<q-input
|
|
||||||
v-model="setList.allowlist"
|
|
||||||
dense
|
|
||||||
filled
|
|
||||||
autogrow
|
|
||||||
></q-input>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="col q-mx-lg items-align flex items-center justify-center"
|
|
||||||
>
|
|
||||||
<q-btn unelevated color="primary" type="submit">
|
|
||||||
Update Allow List
|
|
||||||
</q-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-form>
|
|
||||||
</q-tab-panel>
|
|
||||||
</q-tab-panels>
|
|
||||||
</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'
|
|
||||||
|
|
||||||
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 {
|
|
||||||
listSelection: 'denylist',
|
|
||||||
setList: {
|
|
||||||
allowlist: '',
|
|
||||||
denylist: ''
|
|
||||||
},
|
|
||||||
nostrrelayLinks: [],
|
|
||||||
filter: '',
|
|
||||||
nostrTable: {
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
name: 'wah',
|
|
||||||
align: 'left',
|
|
||||||
label: 'id',
|
|
||||||
field: 'id'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'relay',
|
|
||||||
align: 'left',
|
|
||||||
label: 'relay',
|
|
||||||
field: 'relay'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'status',
|
|
||||||
align: 'left',
|
|
||||||
label: 'status',
|
|
||||||
field: 'status'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
setAllowList: function () {
|
|
||||||
var self = this
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'POST',
|
|
||||||
'/nostradmin/api/v1/allowlist',
|
|
||||||
self.g.user.wallets[0].adminkey,
|
|
||||||
self.allowList
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
if (response.data) {
|
|
||||||
self.allowList = response.data
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
setRelayList: function () {
|
|
||||||
var self = this
|
|
||||||
console.log(self.setList)
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'POST',
|
|
||||||
'/nostradmin/api/v1/setlist',
|
|
||||||
self.g.user.wallets[0].adminkey,
|
|
||||||
self.setList
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
if (response.data) {
|
|
||||||
console.log(response.data)
|
|
||||||
// self.denyList = response.data
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getLists: function () {
|
|
||||||
var self = this
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'GET',
|
|
||||||
'/nostradmin/api/v1/relaylist',
|
|
||||||
self.g.user.wallets[0].adminkey
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
if (response.data) {
|
|
||||||
console.log(response.data)
|
|
||||||
self.setList.denylist = response.data.denylist
|
|
||||||
self.setList.allowlist = response.data.allowlist
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
exportlnurldeviceCSV: function () {
|
|
||||||
var self = this
|
|
||||||
LNbits.utils.exportCSV(self.nostrTable.columns, this.nostrLinks)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created: function () {
|
|
||||||
var self = this
|
|
||||||
this.getRelays()
|
|
||||||
this.getLists()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
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 nostradmin_ext, nostr_renderer
|
|
||||||
# FastAPI good for incoming
|
|
||||||
from fastapi import Request, WebSocket, WebSocketDisconnect
|
|
||||||
# Websockets needed for outgoing
|
|
||||||
import websockets
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
from .crud import get_nostrkeys, get_nostrrelay
|
|
||||||
from .relay_manager import RelayManager, Relay
|
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="templates")
|
|
||||||
|
|
||||||
nostradmin = True
|
|
||||||
|
|
||||||
@nostradmin_ext.get("/", response_class=HTMLResponse)
|
|
||||||
async def index(request: Request, user: User = Depends(check_user_exists)):
|
|
||||||
return nostr_renderer().TemplateResponse(
|
|
||||||
"nostradmin/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)
|
|
||||||
|
|
||||||
|
|
||||||
mgr: RelayManager = RelayManager(enable_ws_debugger=False)
|
|
||||||
|
|
||||||
# listen for events coming from relays
|
|
||||||
|
|
||||||
|
|
||||||
async def connectToNostr():
|
|
||||||
while True:
|
|
||||||
e = await mgr.msg_channel.get()
|
|
||||||
print(e)
|
|
||||||
connectToNostr
|
|
||||||
#####################################################################
|
|
||||||
################### 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()
|
|
||||||
|
|
||||||
|
|
||||||
@nostradmin_ext.websocket("/nostradmin/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":
|
|
||||||
r = Relay(url=relay, read=True, write=True, active=True)
|
|
||||||
try:
|
|
||||||
await mgr.add_relay(r)
|
|
||||||
except:
|
|
||||||
None
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
import asyncio
|
|
||||||
from fastapi import Request
|
|
||||||
from fastapi.param_functions import Query
|
|
||||||
from fastapi.params import Depends
|
|
||||||
from starlette.exceptions import HTTPException
|
|
||||||
|
|
||||||
from lnbits.core.crud import get_user
|
|
||||||
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
|
|
||||||
from lnbits.utils.exchange_rates import currencies
|
|
||||||
|
|
||||||
from lnbits.settings import LNBITS_ADMIN_USERS
|
|
||||||
from . import nostradmin_ext
|
|
||||||
from .crud import (
|
|
||||||
create_nostrkeys,
|
|
||||||
get_nostrkeys,
|
|
||||||
create_nostrnotes,
|
|
||||||
get_nostrnotes,
|
|
||||||
create_nostrrelays,
|
|
||||||
get_nostrrelays,
|
|
||||||
get_nostrrelaylist,
|
|
||||||
update_nostrrelaysetlist,
|
|
||||||
create_nostrconnections,
|
|
||||||
get_nostrconnections,
|
|
||||||
)
|
|
||||||
from .models import nostrKeys, nostrCreateRelays, nostrRelaySetList
|
|
||||||
from .views import relay_check
|
|
||||||
|
|
||||||
@nostradmin_ext.get("/api/v1/relays")
|
|
||||||
async def api_relays_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
|
|
||||||
relays = await get_nostrrelays()
|
|
||||||
if not relays:
|
|
||||||
await create_nostrrelays(nostrCreateRelays(relay="wss://relayer.fiatjaf.com"))
|
|
||||||
await create_nostrrelays(
|
|
||||||
nostrCreateRelays(relay="wss://nostr-pub.wellorder.net")
|
|
||||||
)
|
|
||||||
relays = await get_nostrrelays()
|
|
||||||
if not relays:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
for relay in relays:
|
|
||||||
relay.status = await relay_check(relay.relay)
|
|
||||||
return relays
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@nostradmin_ext.get("/api/v1/relaylist")
|
|
||||||
async def api_relaylist(wallet: WalletTypeInfo = Depends(get_key_type)):
|
|
||||||
if wallet.wallet.user not in LNBITS_ADMIN_USERS:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
|
|
||||||
)
|
|
||||||
return await get_nostrrelaylist()
|
|
||||||
|
|
||||||
@nostradmin_ext.post("/api/v1/setlist")
|
|
||||||
async def api_relayssetlist(data: nostrRelaySetList, wallet: WalletTypeInfo = Depends(get_key_type)):
|
|
||||||
if wallet.wallet.user not in LNBITS_ADMIN_USERS:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
|
|
||||||
)
|
|
||||||
return await update_nostrrelaysetlist(data)
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue