mirror of
https://github.com/aljazceru/nutshell.git
synced 2026-01-10 12:14:20 +01:00
Another changes for LNbits API (#739)
* pending -> status; sse -> websocket * paid_invoices_stream only for incoming * fix backward compatibility * loop * 'retro-compatible' paid invoice stream --------- Co-authored-by: lollerfirst <lollerfirst@gmail.com>
This commit is contained in:
@@ -7,6 +7,8 @@ import httpx
|
|||||||
from bolt11 import (
|
from bolt11 import (
|
||||||
decode,
|
decode,
|
||||||
)
|
)
|
||||||
|
from loguru import logger
|
||||||
|
from websockets.client import connect
|
||||||
|
|
||||||
from ..core.base import Amount, MeltQuote, Unit
|
from ..core.base import Amount, MeltQuote, Unit
|
||||||
from ..core.helpers import fee_reserve
|
from ..core.helpers import fee_reserve
|
||||||
@@ -39,6 +41,8 @@ class LNbitsWallet(LightningBackend):
|
|||||||
verify=not settings.debug,
|
verify=not settings.debug,
|
||||||
headers={"X-Api-Key": settings.mint_lnbits_key},
|
headers={"X-Api-Key": settings.mint_lnbits_key},
|
||||||
)
|
)
|
||||||
|
self.ws_url = f"{self.endpoint.replace('http', 'ws', 1)}/api/v1/ws/{settings.mint_lnbits_key}"
|
||||||
|
self.old_api = True
|
||||||
|
|
||||||
async def status(self) -> StatusResponse:
|
async def status(self) -> StatusResponse:
|
||||||
try:
|
try:
|
||||||
@@ -154,11 +158,13 @@ class LNbitsWallet(LightningBackend):
|
|||||||
result=PaymentResult.UNKNOWN, error_message=data["detail"]
|
result=PaymentResult.UNKNOWN, error_message=data["detail"]
|
||||||
)
|
)
|
||||||
|
|
||||||
if data["paid"]:
|
status = data.get("details", {}).get("status", None)
|
||||||
|
|
||||||
|
if data.get("paid", False):
|
||||||
result = PaymentResult.SETTLED
|
result = PaymentResult.SETTLED
|
||||||
elif not data["paid"] and data["details"]["pending"]:
|
elif status == "pending" or data.get("details", {}).get("pending", False):
|
||||||
result = PaymentResult.PENDING
|
result = PaymentResult.PENDING
|
||||||
elif not data["paid"] and not data["details"]["pending"]:
|
elif status == "failed":
|
||||||
result = PaymentResult.FAILED
|
result = PaymentResult.FAILED
|
||||||
else:
|
else:
|
||||||
result = PaymentResult.UNKNOWN
|
result = PaymentResult.UNKNOWN
|
||||||
@@ -190,11 +196,13 @@ class LNbitsWallet(LightningBackend):
|
|||||||
result=PaymentResult.UNKNOWN, error_message="invalid response"
|
result=PaymentResult.UNKNOWN, error_message="invalid response"
|
||||||
)
|
)
|
||||||
|
|
||||||
if data["paid"]:
|
status = data.get("details", {}).get("status", None)
|
||||||
|
|
||||||
|
if data.get("paid", False):
|
||||||
result = PaymentResult.SETTLED
|
result = PaymentResult.SETTLED
|
||||||
elif not data["paid"] and data["details"]["pending"]:
|
elif status == "pending" or data.get("details", {}).get("pending", False):
|
||||||
result = PaymentResult.PENDING
|
result = PaymentResult.PENDING
|
||||||
elif not data["paid"] and not data["details"]["pending"]:
|
elif status == "failed":
|
||||||
result = PaymentResult.FAILED
|
result = PaymentResult.FAILED
|
||||||
else:
|
else:
|
||||||
result = PaymentResult.UNKNOWN
|
result = PaymentResult.UNKNOWN
|
||||||
@@ -221,40 +229,70 @@ class LNbitsWallet(LightningBackend):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||||
url = f"{self.endpoint}/api/v1/payments/sse"
|
# --- LNBITS RETRO-COMPATIBILITY ---
|
||||||
|
if self.old_api:
|
||||||
|
url = f"{self.endpoint}/api/v1/payments/sse"
|
||||||
|
|
||||||
|
try:
|
||||||
|
sse_headers = self.client.headers.copy()
|
||||||
|
sse_headers.update(
|
||||||
|
{
|
||||||
|
"accept": "text/event-stream",
|
||||||
|
"cache-control": "no-cache",
|
||||||
|
"connection": "keep-alive",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
async with self.client.stream(
|
||||||
|
"GET",
|
||||||
|
url,
|
||||||
|
content="text/event-stream",
|
||||||
|
timeout=None,
|
||||||
|
headers=sse_headers,
|
||||||
|
) as r:
|
||||||
|
sse_trigger = False
|
||||||
|
async for line in r.aiter_lines():
|
||||||
|
if "Payment does not exist." in line:
|
||||||
|
logger.debug("New API detected. Setting old_api = False")
|
||||||
|
self.old_api = False
|
||||||
|
# The data we want to listen to is of this shape:
|
||||||
|
# event: payment-received
|
||||||
|
# data: {.., "payment_hash" : "asd"}
|
||||||
|
if line.startswith("event: payment-received"):
|
||||||
|
sse_trigger = True
|
||||||
|
continue
|
||||||
|
elif sse_trigger and line.startswith("data:"):
|
||||||
|
data = json.loads(line[len("data:") :])
|
||||||
|
sse_trigger = False
|
||||||
|
yield data["payment_hash"]
|
||||||
|
else:
|
||||||
|
sse_trigger = False
|
||||||
|
|
||||||
|
except (OSError, httpx.ReadError, httpx.ConnectError, httpx.ReadTimeout):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if self.old_api:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
return
|
||||||
|
# --- END LNBITS RETRO-COMPATIBILITY ---
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sse_headers = self.client.headers.copy()
|
async with connect(self.ws_url) as ws:
|
||||||
sse_headers.update(
|
logger.info("connected to LNbits fundingsource websocket.")
|
||||||
{
|
while True:
|
||||||
"accept": "text/event-stream",
|
message = await ws.recv()
|
||||||
"cache-control": "no-cache",
|
message_dict = json.loads(message)
|
||||||
"connection": "keep-alive",
|
if (
|
||||||
}
|
message_dict
|
||||||
|
and message_dict.get("payment")
|
||||||
|
and message_dict["payment"].get("payment_hash")
|
||||||
|
and message_dict["payment"].get("amount") > 0
|
||||||
|
):
|
||||||
|
payment_hash = message_dict["payment"]["payment_hash"]
|
||||||
|
logger.info(f"payment-received: {payment_hash}")
|
||||||
|
yield payment_hash
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(
|
||||||
|
f"lost connection to LNbits fundingsource websocket: '{exc}'"
|
||||||
|
"retrying in 5 seconds"
|
||||||
)
|
)
|
||||||
async with self.client.stream(
|
await asyncio.sleep(5)
|
||||||
"GET",
|
|
||||||
url,
|
|
||||||
content="text/event-stream",
|
|
||||||
timeout=None,
|
|
||||||
headers=sse_headers,
|
|
||||||
) as r:
|
|
||||||
sse_trigger = False
|
|
||||||
async for line in r.aiter_lines():
|
|
||||||
# The data we want to listen to is of this shape:
|
|
||||||
# event: payment-received
|
|
||||||
# data: {.., "payment_hash" : "asd"}
|
|
||||||
if line.startswith("event: payment-received"):
|
|
||||||
sse_trigger = True
|
|
||||||
continue
|
|
||||||
elif sse_trigger and line.startswith("data:"):
|
|
||||||
data = json.loads(line[len("data:") :])
|
|
||||||
sse_trigger = False
|
|
||||||
yield data["payment_hash"]
|
|
||||||
else:
|
|
||||||
sse_trigger = False
|
|
||||||
|
|
||||||
except (OSError, httpx.ReadError, httpx.ConnectError, httpx.ReadTimeout):
|
|
||||||
pass
|
|
||||||
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user