Initial commit with translated description
This commit is contained in:
229
scripts/lnbits_cli.py
Normal file
229
scripts/lnbits_cli.py
Normal file
@@ -0,0 +1,229 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import base64
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
try:
|
||||
import qrcode
|
||||
HAS_QRCODE = True
|
||||
except ImportError:
|
||||
HAS_QRCODE = False
|
||||
|
||||
# Configuration
|
||||
BASE_URL = os.getenv("LNBITS_BASE_URL", "https://legend.lnbits.com").rstrip("/")
|
||||
API_KEY = os.getenv("LNBITS_API_KEY")
|
||||
# Save QR codes relative to CWD so the path can be expressed as ./ for media parsers.
|
||||
# The CWD is shared between the Python subprocess and the parent Node.js process.
|
||||
QR_DIR = os.path.join(os.getcwd(), ".lnbits_qr")
|
||||
QR_MAX_AGE_SECONDS = 300 # 5 minutes
|
||||
|
||||
# --- Helpers ---
|
||||
|
||||
def error(msg, code=1):
|
||||
print(json.dumps({"error": msg}))
|
||||
sys.exit(code)
|
||||
|
||||
def cleanup_old_qr_files():
|
||||
"""Remove QR code files older than QR_MAX_AGE_SECONDS."""
|
||||
if not os.path.exists(QR_DIR):
|
||||
return
|
||||
now = time.time()
|
||||
for filename in os.listdir(QR_DIR):
|
||||
if not filename.endswith(".png"):
|
||||
continue
|
||||
filepath = os.path.join(QR_DIR, filename)
|
||||
try:
|
||||
if now - os.path.getmtime(filepath) > QR_MAX_AGE_SECONDS:
|
||||
os.remove(filepath)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def get_qr_path():
|
||||
"""Get a unique path for a new QR code file. Returns (absolute_path, media_path).
|
||||
|
||||
The media_path is a ./ relative path resolved from the gateway's CWD (home dir)
|
||||
since OpenClaw's MEDIA: parser only accepts ./relative paths.
|
||||
"""
|
||||
os.makedirs(QR_DIR, exist_ok=True)
|
||||
cleanup_old_qr_files()
|
||||
filename = f"invoice_{int(time.time() * 1000)}.png"
|
||||
abs_path = os.path.join(QR_DIR, filename)
|
||||
media_path = "./" + os.path.relpath(abs_path, os.path.expanduser("~"))
|
||||
return abs_path, media_path
|
||||
|
||||
def generate_qr(data, output_path=None):
|
||||
"""Generate a QR code. If output_path provided, saves to file and returns path. Otherwise returns base64."""
|
||||
if not HAS_QRCODE:
|
||||
return None
|
||||
qr = qrcode.QRCode(
|
||||
version=1,
|
||||
error_correction=qrcode.constants.ERROR_CORRECT_M,
|
||||
box_size=10,
|
||||
border=4,
|
||||
)
|
||||
qr.add_data(data.upper()) # BOLT11 should be uppercase for QR
|
||||
qr.make(fit=True)
|
||||
img = qr.make_image(fill_color="black", back_color="white")
|
||||
|
||||
if output_path:
|
||||
img.save(output_path, format="PNG")
|
||||
return output_path
|
||||
else:
|
||||
buffer = io.BytesIO()
|
||||
img.save(buffer, format="PNG")
|
||||
buffer.seek(0)
|
||||
return base64.b64encode(buffer.read()).decode("utf-8")
|
||||
|
||||
def request(method, endpoint, data=None):
|
||||
if not API_KEY:
|
||||
error("LNBITS_API_KEY environment variable is not set.")
|
||||
|
||||
url = f"{BASE_URL}/api/v1{endpoint}"
|
||||
headers = {
|
||||
"X-Api-Key": API_KEY,
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "LNbits-CLI/1.0"
|
||||
}
|
||||
body = json.dumps(data).encode("utf-8") if data else None
|
||||
|
||||
req = urllib.request.Request(url, method=method, headers=headers, data=body)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=20) as resp:
|
||||
return json.loads(resp.read().decode("utf-8"))
|
||||
except urllib.error.HTTPError as e:
|
||||
error_body = e.read().decode("utf-8", errors="replace")
|
||||
error(f"API Error ({e.code}): {error_body}")
|
||||
except Exception as e:
|
||||
error(f"Network Error: {str(e)}")
|
||||
|
||||
# --- Core Logic ---
|
||||
|
||||
def get_balance():
|
||||
data = request("GET", "/wallet")
|
||||
return {
|
||||
"name": data.get("name"),
|
||||
"balance_msat": data.get("balance"),
|
||||
"balance_sats": int(data.get("balance", 0) / 1000)
|
||||
}
|
||||
|
||||
def create_invoice(amount, memo, include_qr=True):
|
||||
result = request("POST", "/payments", {
|
||||
"out": False, "amount": amount, "memo": memo, "unit": "sat"
|
||||
})
|
||||
if include_qr and result.get("payment_request"):
|
||||
abs_path, rel_path = get_qr_path()
|
||||
qr_result = generate_qr(result["payment_request"], abs_path)
|
||||
if qr_result:
|
||||
result["qr_file"] = rel_path
|
||||
# Also include base64 for direct embedding
|
||||
with open(abs_path, "rb") as f:
|
||||
b64 = base64.b64encode(f.read()).decode("utf-8")
|
||||
result["qr_base64"] = f"image/png;base64,{b64}"
|
||||
else:
|
||||
result["qr_error"] = "qrcode library not installed (pip install qrcode[pil])"
|
||||
return result
|
||||
|
||||
def pay_invoice(bolt11):
|
||||
return request("POST", "/payments", {"out": True, "bolt11": bolt11})
|
||||
|
||||
def decode_invoice(bolt11):
|
||||
return request("POST", "/payments/decode", {"data": bolt11})
|
||||
|
||||
def create_wallet(name):
|
||||
url = f"{BASE_URL}/api/v1/account"
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
method="POST",
|
||||
headers={"Content-Type": "application/json", "User-Agent": "LNbits-CLI/1.0"},
|
||||
data=json.dumps({"name": name}).encode("utf-8")
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=20) as resp:
|
||||
return json.loads(resp.read().decode("utf-8"))
|
||||
|
||||
# --- CLI Handlers ---
|
||||
|
||||
def cmd_balance(args):
|
||||
print(json.dumps(get_balance(), indent=2))
|
||||
|
||||
def cmd_create(args):
|
||||
print(json.dumps(create_wallet(args.name), indent=2))
|
||||
|
||||
def cmd_invoice(args):
|
||||
print(json.dumps(create_invoice(args.amount, args.memo, not args.no_qr), indent=2))
|
||||
|
||||
def cmd_pay(args):
|
||||
print(json.dumps(pay_invoice(args.bolt11), indent=2))
|
||||
|
||||
def cmd_decode(args):
|
||||
print(json.dumps(decode_invoice(args.bolt11), indent=2))
|
||||
|
||||
def cmd_qr(args):
|
||||
"""Generate QR code from a BOLT11 string."""
|
||||
if args.output:
|
||||
abs_path = args.output
|
||||
rel_path = args.output
|
||||
else:
|
||||
abs_path, rel_path = get_qr_path()
|
||||
qr_result = generate_qr(args.bolt11, abs_path)
|
||||
if qr_result:
|
||||
result = {"qr_file": rel_path, "bolt11": args.bolt11}
|
||||
with open(abs_path, "rb") as f:
|
||||
b64 = base64.b64encode(f.read()).decode("utf-8")
|
||||
result["qr_base64"] = f"image/png;base64,{b64}"
|
||||
print(json.dumps(result, indent=2))
|
||||
else:
|
||||
error("qrcode library not installed (pip install qrcode[pil])")
|
||||
|
||||
# --- Main ---
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="LNbits CLI Bridge for Clawdbot")
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
# Balance
|
||||
p_balance = subparsers.add_parser("balance", help="Get wallet balance")
|
||||
p_balance.set_defaults(func=cmd_balance)
|
||||
|
||||
# Create Wallet (Account)
|
||||
p_create = subparsers.add_parser("create", help="Create a new LNbits wallet")
|
||||
p_create.add_argument("--name", type=str, default="Moltbot Wallet", help="Name of the new wallet")
|
||||
p_create.set_defaults(func=cmd_create)
|
||||
|
||||
# Invoice
|
||||
p_invoice = subparsers.add_parser("invoice", help="Create a lightning invoice")
|
||||
p_invoice.add_argument("--amount", type=int, required=True, help="Amount in satoshis")
|
||||
p_invoice.add_argument("--memo", type=str, default="", help="Optional memo")
|
||||
p_invoice.add_argument("--no-qr", action="store_true", dest="no_qr", help="Skip QR code generation")
|
||||
p_invoice.set_defaults(func=cmd_invoice)
|
||||
|
||||
# Pay
|
||||
p_pay = subparsers.add_parser("pay", help="Pay a lightning invoice")
|
||||
p_pay.add_argument("bolt11", type=str, help="The Bolt11 invoice string")
|
||||
p_pay.set_defaults(func=cmd_pay)
|
||||
|
||||
# Decode
|
||||
p_decode = subparsers.add_parser("decode", help="Decode a lightning invoice")
|
||||
p_decode.add_argument("bolt11", type=str, help="The Bolt11 invoice string")
|
||||
p_decode.set_defaults(func=cmd_decode)
|
||||
|
||||
# QR Code
|
||||
p_qr = subparsers.add_parser("qr", help="Generate QR code from a BOLT11 invoice")
|
||||
p_qr.add_argument("bolt11", type=str, help="The Bolt11 invoice string")
|
||||
p_qr.add_argument("-o", "--output", type=str, help="Output PNG file path (default: auto-generated temp file)")
|
||||
p_qr.set_defaults(func=cmd_qr)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
args.func(args)
|
||||
except Exception as e:
|
||||
error(str(e))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user