merge main

This commit is contained in:
callebtc
2022-12-02 11:23:20 +01:00
169 changed files with 8659 additions and 1466 deletions
+18 -32
View File
@@ -229,6 +229,24 @@ async def get_wallet_payment(
return Payment.from_row(row) if row else None
async def get_latest_payments_by_extension(ext_name: str, ext_id: str, limit: int = 5):
rows = await db.fetchall(
f"""
SELECT * FROM apipayments
WHERE pending = 'false'
AND extra LIKE ?
AND extra LIKE ?
ORDER BY time DESC LIMIT {limit}
""",
(
f"%{ext_name}%",
f"%{ext_id}%",
),
)
return rows
async def get_payments(
*,
wallet_id: Optional[str] = None,
@@ -330,38 +348,6 @@ async def delete_expired_invoices(
"""
)
# # then we delete all expired invoices, checking one by one
# rows = await (conn or db).fetchall(
# f"""
# SELECT bolt11
# FROM apipayments
# WHERE pending = true
# AND bolt11 IS NOT NULL
# AND amount > 0 AND time < {db.timestamp_now} - {db.interval_seconds(86400)}
# """
# )
# logger.debug(f"Checking expiry of {len(rows)} invoices")
# for (payment_request,) in rows:
# try:
# invoice = bolt11.decode(payment_request)
# except:
# continue
# expiration_date = datetime.datetime.fromtimestamp(invoice.date + invoice.expiry)
# if expiration_date > datetime.datetime.utcnow():
# continue
# logger.debug(
# f"Deleting expired invoice: {invoice.payment_hash} (expired: {expiration_date})"
# )
# await (conn or db).execute(
# """
# DELETE FROM apipayments
# WHERE pending = true AND hash = ?
# """,
# (invoice.payment_hash,),
# )
# payments
# --------
+1 -1
View File
@@ -53,7 +53,7 @@ async def m001_initial(db):
f"""
CREATE TABLE IF NOT EXISTS apipayments (
payhash TEXT NOT NULL,
amount INTEGER NOT NULL,
amount {db.big_int} NOT NULL,
fee INTEGER NOT NULL DEFAULT 0,
wallet TEXT NOT NULL,
pending BOOLEAN NOT NULL,
+27 -2
View File
@@ -2,11 +2,11 @@ import asyncio
import json
from binascii import unhexlify
from io import BytesIO
from typing import Dict, Optional, Tuple
from typing import Dict, List, Optional, Tuple
from urllib.parse import parse_qs, urlparse
import httpx
from fastapi import Depends
from fastapi import Depends, WebSocket, WebSocketDisconnect
from lnurl import LnurlErrorResponse
from lnurl import decode as decode_lnurl # type: ignore
from loguru import logger
@@ -382,3 +382,28 @@ async def check_transaction_status(
# WARN: this same value must be used for balance check and passed to WALLET.pay_invoice(), it may cause a vulnerability if the values differ
def fee_reserve(amount_msat: int) -> int:
return max(int(RESERVE_FEE_MIN), int(amount_msat * RESERVE_FEE_PERCENT / 100.0))
class WebsocketConnectionManager:
def __init__(self):
self.active_connections: List[WebSocket] = []
async def connect(self, websocket: WebSocket):
await websocket.accept()
logger.debug(websocket)
self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
async def send_data(self, message: str, item_id: str):
for connection in self.active_connections:
if connection.path_params["item_id"] == item_id:
await connection.send_text(message)
websocketManager = WebsocketConnectionManager()
async def websocketUpdater(item_id, data):
return await websocketManager.send_data(f"{data}", item_id)
+29
View File
@@ -361,6 +361,35 @@ new Vue({
this.receive.status = 'pending'
})
},
onInitQR: async function (promise) {
try {
await promise
} catch (error) {
let mapping = {
NotAllowedError: 'ERROR: you need to grant camera access permission',
NotFoundError: 'ERROR: no camera on this device',
NotSupportedError:
'ERROR: secure context required (HTTPS, localhost)',
NotReadableError: 'ERROR: is the camera already in use?',
OverconstrainedError: 'ERROR: installed cameras are not suitable',
StreamApiNotSupportedError:
'ERROR: Stream API is not supported in this browser',
InsecureContextError:
'ERROR: Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP.'
}
let valid_error = Object.keys(mapping).filter(key => {
return error.name === key
})
let camera_error = valid_error
? mapping[valid_error]
: `ERROR: Camera error (${error.name})`
this.parse.camera.show = false
this.$q.notify({
message: camera_error,
type: 'negative'
})
}
},
decodeQR: function (res) {
this.parse.data.request = res
this.decodeRequest()
+28
View File
@@ -171,7 +171,35 @@
</a>
</div>
</div>
<div class="row">
<div class="col">
<a href="https://mynodebtc.com">
<q-img
contain
:src="($q.dark.isActive) ? '/static/images/mynode.png' : '/static/images/mynodel.png'"
></q-img>
</a>
</div>
<div class="col q-pl-md">&nbsp;</div>
</div>
</div>
{% if AD_SPACE %} {% for ADS in AD_SPACE %} {% set AD = ADS.split(';') %}
<div class="col-6 col-sm-4 col-md-8 q-gutter-y-sm">
<q-btn flat color="secondary" class="full-width q-mb-md"
>{{ AD_TITLE }}</q-btn
>
<a href="{{ AD[0] }}" class="q-ma-md">
<img
v-if="($q.dark.isActive)"
src="{{ AD[1] }}"
style="max-width: 90%"
/>
<img v-else src="{{ AD[2] }}" style="max-width: 90%" />
</a>
</div>
{% endfor %} {% endif %}
</div>
</div>
</div>
+10 -3
View File
@@ -388,9 +388,14 @@
{% endif %} {% if AD_SPACE %} {% for ADS in AD_SPACE %} {% set AD =
ADS.split(';') %}
<q-card>
<a href="{{ AD[0] }}"
><img width="100%" src="{{ AD[1] }}"
/></a> </q-card
<q-card-section>
<h6 class="text-subtitle1 q-mt-none q-mb-sm">{{ AD_TITLE }}</h6>
</q-card-section>
<q-card-section class="q-pa-none">
<a href="{{ AD[0] }}" class="q-ma-md">
<img v-if="($q.dark.isActive)" src="{{ AD[1] }}" />
<img v-else src="{{ AD[2] }}" />
</a> </q-card-section></q-card
>{% endfor %} {% endif %}
</div>
</div>
@@ -653,6 +658,7 @@
<q-responsive :ratio="1">
<qrcode-stream
@decode="decodeQR"
@init="onInitQR"
class="rounded-borders"
></qrcode-stream>
</q-responsive>
@@ -671,6 +677,7 @@
<div class="text-center q-mb-lg">
<qrcode-stream
@decode="decodeQR"
@init="onInitQR"
class="rounded-borders"
></qrcode-stream>
</div>
+59 -18
View File
@@ -12,7 +12,15 @@ from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse
import async_timeout
import httpx
import pyqrcode
from fastapi import Depends, Header, Query, Request
from fastapi import (
Depends,
Header,
Query,
Request,
Response,
WebSocket,
WebSocketDisconnect,
)
from fastapi.exceptions import HTTPException
from fastapi.params import Body
from loguru import logger
@@ -56,6 +64,8 @@ from ..services import (
create_invoice,
pay_invoice,
perform_lnurlauth,
websocketManager,
websocketUpdater,
)
from ..tasks import api_invoice_listeners
@@ -155,30 +165,29 @@ class CreateInvoiceData(BaseModel):
async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet):
if data.description_hash:
if data.description_hash or data.unhashed_description:
try:
description_hash = binascii.unhexlify(data.description_hash)
description_hash = (
binascii.unhexlify(data.description_hash)
if data.description_hash
else b""
)
unhashed_description = (
binascii.unhexlify(data.unhashed_description)
if data.unhashed_description
else b""
)
except binascii.Error:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail="'description_hash' must be a valid hex string",
detail="'description_hash' and 'unhashed_description' must be a valid hex strings",
)
unhashed_description = b""
memo = ""
elif data.unhashed_description:
try:
unhashed_description = binascii.unhexlify(data.unhashed_description)
except binascii.Error:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail="'unhashed_description' must be a valid hex string",
)
description_hash = b""
memo = ""
else:
description_hash = b""
unhashed_description = b""
memo = data.memo or LNBITS_SITE_TITLE
if data.unit == "sat":
amount = int(data.amount)
else:
@@ -476,7 +485,7 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type
except:
# parse internet identifier (user@domain.com)
name_domain = code.split("@")
if len(name_domain) == 2 and len(name_domain[1].split(".")) == 2:
if len(name_domain) == 2 and len(name_domain[1].split(".")) >= 2:
name, domain = name_domain
url = (
("http://" if domain.endswith(".onion") else "https://")
@@ -585,8 +594,8 @@ class DecodePayment(BaseModel):
data: str
@core_app.post("/api/v1/payments/decode")
async def api_payments_decode(data: DecodePayment):
@core_app.post("/api/v1/payments/decode", status_code=HTTPStatus.OK)
async def api_payments_decode(data: DecodePayment, response: Response):
payment_str = data.data
try:
if payment_str[:5] == "LNURL":
@@ -607,6 +616,7 @@ async def api_payments_decode(data: DecodePayment):
"min_final_cltv_expiry": invoice.min_final_cltv_expiry,
}
except:
response.status_code = HTTPStatus.BAD_REQUEST
return {"message": "Failed to decode"}
@@ -697,3 +707,34 @@ async def api_auditor(wallet: WalletTypeInfo = Depends(get_key_type)):
"delta_msats": delta,
"timestamp": int(time.time()),
}
##################UNIVERSAL WEBSOCKET MANAGER########################
@core_app.websocket("/api/v1/ws/{item_id}")
async def websocket_connect(websocket: WebSocket, item_id: str):
await websocketManager.connect(websocket)
try:
while True:
data = await websocket.receive_text()
except WebSocketDisconnect:
websocketManager.disconnect(websocket)
@core_app.post("/api/v1/ws/{item_id}")
async def websocket_update_post(item_id: str, data: str):
try:
await websocketUpdater(item_id, data)
return {"sent": True, "data": data}
except:
return {"sent": False, "data": data}
@core_app.get("/api/v1/ws/{item_id}/{data}")
async def websocket_update_get(item_id: str, data: str):
try:
await websocketUpdater(item_id, data)
return {"sent": True, "data": data}
except:
return {"sent": False, "data": data}