diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index c33e874cb..b533a8e2c 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -34,6 +34,7 @@ from lnbits.utils.exchange_rates import ( fiat_amount_as_satoshis, satoshis_amount_as_fiat, ) + from .. import core_app, db from ..crud import ( create_payment, diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index fcc0365bf..31a7b0300 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -22,6 +22,8 @@ from lnbits.settings import ( LNBITS_SITE_TITLE, SERVICE_FEE, ) + +from ...helpers import get_valid_extensions from ..crud import ( create_account, create_wallet, @@ -32,7 +34,6 @@ from ..crud import ( update_user_extension, ) from ..services import pay_invoice, redeem_lnurl_withdraw -from ...helpers import get_valid_extensions core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"]) diff --git a/lnbits/core/views/public_api.py b/lnbits/core/views/public_api.py index 465693d9a..ef8dc056c 100644 --- a/lnbits/core/views/public_api.py +++ b/lnbits/core/views/public_api.py @@ -8,6 +8,7 @@ from loguru import logger from starlette.requests import Request from lnbits import bolt11 + from .. import core_app from ..crud import get_standalone_payment from ..tasks import api_invoice_listeners diff --git a/lnbits/extensions/gerty/__init__.py b/lnbits/extensions/gerty/__init__.py index c5f526b54..03fdef12b 100644 --- a/lnbits/extensions/gerty/__init__.py +++ b/lnbits/extensions/gerty/__init__.py @@ -11,8 +11,10 @@ db = Database("ext_gerty") gerty_ext: APIRouter = APIRouter(prefix="/gerty", tags=["Gerty"]) + def gerty_renderer(): return template_renderer(["lnbits/extensions/gerty/templates"]) + from .views import * # noqa from .views_api import * # noqa diff --git a/lnbits/extensions/gerty/crud.py b/lnbits/extensions/gerty/crud.py index a472ef37f..10b17df1e 100644 --- a/lnbits/extensions/gerty/crud.py +++ b/lnbits/extensions/gerty/crud.py @@ -28,7 +28,7 @@ async def create_gerty(wallet_id: str, data: Gerty) -> Gerty: data.lnbits_wallets, data.mempool_endpoint, data.exchange, - data.display_preferences + data.display_preferences, ), ) @@ -36,6 +36,7 @@ async def create_gerty(wallet_id: str, data: Gerty) -> Gerty: assert gerty, "Newly created gerty couldn't be retrieved" return gerty + async def update_gerty(gerty_id: str, **kwargs) -> Gerty: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) await db.execute( @@ -43,6 +44,7 @@ async def update_gerty(gerty_id: str, **kwargs) -> Gerty: ) return await get_gerty(gerty_id) + async def get_gerty(gerty_id: str) -> Optional[Gerty]: row = await db.fetchone("SELECT * FROM gerty.gertys WHERE id = ?", (gerty_id,)) return Gerty(**row) if row else None diff --git a/lnbits/extensions/gerty/helpers.py b/lnbits/extensions/gerty/helpers.py index 3f7f88e3e..b2c757a68 100644 --- a/lnbits/extensions/gerty/helpers.py +++ b/lnbits/extensions/gerty/helpers.py @@ -1,15 +1,18 @@ -from datetime import datetime, timedelta -import pytz -import httpx import textwrap +from datetime import datetime, timedelta + +import httpx +import pytz from loguru import logger from .number_prefixer import * + def get_percent_difference(current, previous, precision=4): difference = (current - previous) / current * 100 return "{0}{1}%".format("+" if difference > 0 else "", round(difference, precision)) + # A helper function get a nicely formated dict for the text def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int = None): # Get line size by font size @@ -30,26 +33,24 @@ def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int word_list = wrapper.wrap(text=text) # logger.debug("number of chars = {0}".format(len(text))) - multilineText = '\n'.join(word_list) + multilineText = "\n".join(word_list) # logger.debug("number of lines = {0}".format(len(word_list))) # logger.debug('multilineText') # logger.debug(multilineText) - text = { - "value": multilineText, - "size": font_size - } + text = {"value": multilineText, "size": font_size} if x_pos is None and y_pos is None: - text['position'] = 'center' + text["position"] = "center" else: - text['x'] = x_pos - text['y'] = y_pos + text["x"] = x_pos + text["y"] = y_pos return text + # format a number for nice display output def format_number(number, precision=None): - return ("{:,}".format(round(number, precision))) + return "{:,}".format(round(number, precision)) async def get_mempool_recommended_fees(gerty): @@ -58,6 +59,7 @@ async def get_mempool_recommended_fees(gerty): r = await client.get(gerty.mempool_endpoint + "/api/v1/fees/recommended") return r.json() + async def get_mining_dashboard(gerty): areas = [] if isinstance(gerty.mempool_endpoint, str): @@ -65,94 +67,141 @@ async def get_mining_dashboard(gerty): # current hashrate r = await client.get(gerty.mempool_endpoint + "/api/v1/mining/hashrate/1w") data = r.json() - hashrateNow = data['currentHashrate'] - hashrateOneWeekAgo = data['hashrates'][6]['avgHashrate'] + hashrateNow = data["currentHashrate"] + hashrateOneWeekAgo = data["hashrates"][6]["avgHashrate"] text = [] text.append(get_text_item_dict("Current mining hashrate", 12)) - text.append(get_text_item_dict("{0}hash".format(si_format(hashrateNow, 6, True, " ")), 20)) - text.append(get_text_item_dict("{0} vs 7 days ago".format(get_percent_difference(hashrateNow, hashrateOneWeekAgo, 3)), 12)) + text.append( + get_text_item_dict( + "{0}hash".format(si_format(hashrateNow, 6, True, " ")), 20 + ) + ) + text.append( + get_text_item_dict( + "{0} vs 7 days ago".format( + get_percent_difference(hashrateNow, hashrateOneWeekAgo, 3) + ), + 12, + ) + ) areas.append(text) - r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment") + r = await client.get( + gerty.mempool_endpoint + "/api/v1/difficulty-adjustment" + ) # timeAvg text = [] - progress = "{0}%".format(round(r.json()['progressPercent'], 2)) + progress = "{0}%".format(round(r.json()["progressPercent"], 2)) text.append(get_text_item_dict("Progress through current epoch", 12)) text.append(get_text_item_dict(progress, 20)) areas.append(text) # difficulty adjustment text = [] - stat = r.json()['remainingTime'] + stat = r.json()["remainingTime"] text.append(get_text_item_dict("Time to next difficulty adjustment", 12)) text.append(get_text_item_dict(get_time_remaining(stat / 1000, 3), 20)) areas.append(text) # difficultyChange text = [] - difficultyChange = round(r.json()['difficultyChange'], 2) + difficultyChange = round(r.json()["difficultyChange"], 2) text.append(get_text_item_dict("Estimated difficulty change", 12)) - text.append(get_text_item_dict("{0}{1}%".format("+" if difficultyChange > 0 else "", round(difficultyChange, 2)), 20)) + text.append( + get_text_item_dict( + "{0}{1}%".format( + "+" if difficultyChange > 0 else "", round(difficultyChange, 2) + ), + 20, + ) + ) areas.append(text) r = await client.get(gerty.mempool_endpoint + "/api/v1/mining/hashrate/1m") data = r.json() stat = {} - stat['current'] = data['currentDifficulty'] - stat['previous'] = data['difficulty'][len(data['difficulty']) - 2]['difficulty'] + stat["current"] = data["currentDifficulty"] + stat["previous"] = data["difficulty"][len(data["difficulty"]) - 2][ + "difficulty" + ] return areas + async def api_get_lightning_stats(gerty): stat = {} if isinstance(gerty.mempool_endpoint, str): async with httpx.AsyncClient() as client: - r = await client.get(gerty.mempool_endpoint + "/api/v1/lightning/statistics/latest") + r = await client.get( + gerty.mempool_endpoint + "/api/v1/lightning/statistics/latest" + ) data = r.json() return data + async def get_lightning_stats(gerty): data = await api_get_lightning_stats(gerty) areas = [] text = [] text.append(get_text_item_dict("Channel Count", 12)) - text.append(get_text_item_dict(format_number(data['latest']['channel_count']), 20)) - difference = get_percent_difference(current=data['latest']['channel_count'], - previous=data['previous']['channel_count']) + text.append(get_text_item_dict(format_number(data["latest"]["channel_count"]), 20)) + difference = get_percent_difference( + current=data["latest"]["channel_count"], + previous=data["previous"]["channel_count"], + ) text.append(get_text_item_dict("{0} in last 7 days".format(difference), 12)) areas.append(text) text = [] text.append(get_text_item_dict("Number of Nodes", 12)) - text.append(get_text_item_dict(format_number(data['latest']['node_count']), 20)) - difference = get_percent_difference(current=data['latest']['node_count'], previous=data['previous']['node_count']) + text.append(get_text_item_dict(format_number(data["latest"]["node_count"]), 20)) + difference = get_percent_difference( + current=data["latest"]["node_count"], previous=data["previous"]["node_count"] + ) text.append(get_text_item_dict("{0} in last 7 days".format(difference), 12)) areas.append(text) text = [] text.append(get_text_item_dict("Total Capacity", 12)) - avg_capacity = float(data['latest']['total_capacity']) / float(100000000) - text.append(get_text_item_dict("{0} BTC".format(format_number(avg_capacity, 2)), 20)) - difference = get_percent_difference(current=data['latest']['total_capacity'], previous=data['previous']['total_capacity']) + avg_capacity = float(data["latest"]["total_capacity"]) / float(100000000) + text.append( + get_text_item_dict("{0} BTC".format(format_number(avg_capacity, 2)), 20) + ) + difference = get_percent_difference( + current=data["latest"]["total_capacity"], + previous=data["previous"]["total_capacity"], + ) text.append(get_text_item_dict("{0} in last 7 days".format(difference), 12)) areas.append(text) text = [] text.append(get_text_item_dict("Average Channel Capacity", 12)) - text.append(get_text_item_dict("{0} sats".format(format_number(data['latest']['avg_capacity'])), 20)) - difference = get_percent_difference(current=data['latest']['avg_capacity'], previous=data['previous']['avg_capacity']) + text.append( + get_text_item_dict( + "{0} sats".format(format_number(data["latest"]["avg_capacity"])), 20 + ) + ) + difference = get_percent_difference( + current=data["latest"]["avg_capacity"], + previous=data["previous"]["avg_capacity"], + ) text.append(get_text_item_dict("{0} in last 7 days".format(difference), 12)) areas.append(text) return areas + def get_next_update_time(sleep_time_seconds: int = 0, timezone: str = "Europe/London"): utc_now = pytz.utc.localize(datetime.utcnow()) next_refresh_time = utc_now + timedelta(0, sleep_time_seconds) local_refresh_time = next_refresh_time.astimezone(pytz.timezone(timezone)) - return "{0} {1}".format("I'll wake up at" if gerty_should_sleep() else "Next update at",local_refresh_time.strftime("%H:%M on %e %b %Y")) + return "{0} {1}".format( + "I'll wake up at" if gerty_should_sleep() else "Next update at", + local_refresh_time.strftime("%H:%M on %e %b %Y"), + ) + def gerty_should_sleep(timezone: str = "Europe/London"): utc_now = pytz.utc.localize(datetime.utcnow()) @@ -161,13 +210,12 @@ def gerty_should_sleep(timezone: str = "Europe/London"): hours = int(hours) logger.debug("HOURS") logger.debug(hours) - if(hours >= 22 and hours <= 23): + if hours >= 22 and hours <= 23: return True else: return False - def get_date_suffix(dayNumber): if 4 <= dayNumber <= 20 or 24 <= dayNumber <= 30: return "th" @@ -178,10 +226,10 @@ def get_date_suffix(dayNumber): def get_time_remaining(seconds, granularity=2): intervals = ( # ('weeks', 604800), # 60 * 60 * 24 * 7 - ('days', 86400), # 60 * 60 * 24 - ('hours', 3600), # 60 * 60 - ('minutes', 60), - ('seconds', 1), + ("days", 86400), # 60 * 60 * 24 + ("hours", 3600), # 60 * 60 + ("minutes", 60), + ("seconds", 1), ) result = [] @@ -191,6 +239,6 @@ def get_time_remaining(seconds, granularity=2): if value: seconds -= value * count if value == 1: - name = name.rstrip('s') + name = name.rstrip("s") result.append("{} {}".format(round(value), name)) - return ', '.join(result[:granularity]) + return ", ".join(result[:granularity]) diff --git a/lnbits/extensions/gerty/migrations.py b/lnbits/extensions/gerty/migrations.py index 459fc8807..0e15b68e2 100644 --- a/lnbits/extensions/gerty/migrations.py +++ b/lnbits/extensions/gerty/migrations.py @@ -15,4 +15,4 @@ async def m001_initial(db): display_preferences TEXT ); """ - ) \ No newline at end of file + ) diff --git a/lnbits/extensions/gerty/models.py b/lnbits/extensions/gerty/models.py index fc7a33774..89707a86a 100644 --- a/lnbits/extensions/gerty/models.py +++ b/lnbits/extensions/gerty/models.py @@ -4,16 +4,21 @@ from typing import Optional from fastapi import Query from pydantic import BaseModel + class Gerty(BaseModel): id: str = Query(None) name: str wallet: str refresh_time: int = Query(None) - lnbits_wallets: str = Query(None) # Wallets to keep an eye on, {"wallet-id": "wallet-read-key, etc"} - mempool_endpoint: str = Query(None) # Mempool endpoint to use - exchange: str = Query(None) # BTC <-> Fiat exchange rate to pull ie "USD", in 0.0001 and sats + lnbits_wallets: str = Query( + None + ) # Wallets to keep an eye on, {"wallet-id": "wallet-read-key, etc"} + mempool_endpoint: str = Query(None) # Mempool endpoint to use + exchange: str = Query( + None + ) # BTC <-> Fiat exchange rate to pull ie "USD", in 0.0001 and sats display_preferences: str = Query(None) @classmethod def from_row(cls, row: Row) -> "Gerty": - return cls(**dict(row)) \ No newline at end of file + return cls(**dict(row)) diff --git a/lnbits/extensions/gerty/number_prefixer.py b/lnbits/extensions/gerty/number_prefixer.py index 1ba8c024b..eab684e7a 100644 --- a/lnbits/extensions/gerty/number_prefixer.py +++ b/lnbits/extensions/gerty/number_prefixer.py @@ -1,48 +1,51 @@ import math + def si_classifier(val): suffixes = { - 24:{'long_suffix':'yotta', 'short_suffix':'Y', 'scalar':10**24}, - 21:{'long_suffix':'zetta', 'short_suffix':'Z', 'scalar':10**21}, - 18:{'long_suffix':'exa', 'short_suffix':'E', 'scalar':10**18}, - 15:{'long_suffix':'peta', 'short_suffix':'P', 'scalar':10**15}, - 12:{'long_suffix':'tera', 'short_suffix':'T', 'scalar':10**12}, - 9:{'long_suffix':'giga', 'short_suffix':'G', 'scalar':10**9}, - 6:{'long_suffix':'mega', 'short_suffix':'M', 'scalar':10**6}, - 3:{'long_suffix':'kilo', 'short_suffix':'k', 'scalar':10**3}, - 0:{'long_suffix':'', 'short_suffix':'', 'scalar':10**0}, - -3:{'long_suffix':'milli', 'short_suffix':'m', 'scalar':10**-3}, - -6:{'long_suffix':'micro', 'short_suffix':'µ', 'scalar':10**-6}, - -9:{'long_suffix':'nano', 'short_suffix':'n', 'scalar':10**-9}, - -12:{'long_suffix':'pico', 'short_suffix':'p', 'scalar':10**-12}, - -15:{'long_suffix':'femto', 'short_suffix':'f', 'scalar':10**-15}, - -18:{'long_suffix':'atto', 'short_suffix':'a', 'scalar':10**-18}, - -21:{'long_suffix':'zepto', 'short_suffix':'z', 'scalar':10**-21}, - -24:{'long_suffix':'yocto', 'short_suffix':'y', 'scalar':10**-24} + 24: {"long_suffix": "yotta", "short_suffix": "Y", "scalar": 10**24}, + 21: {"long_suffix": "zetta", "short_suffix": "Z", "scalar": 10**21}, + 18: {"long_suffix": "exa", "short_suffix": "E", "scalar": 10**18}, + 15: {"long_suffix": "peta", "short_suffix": "P", "scalar": 10**15}, + 12: {"long_suffix": "tera", "short_suffix": "T", "scalar": 10**12}, + 9: {"long_suffix": "giga", "short_suffix": "G", "scalar": 10**9}, + 6: {"long_suffix": "mega", "short_suffix": "M", "scalar": 10**6}, + 3: {"long_suffix": "kilo", "short_suffix": "k", "scalar": 10**3}, + 0: {"long_suffix": "", "short_suffix": "", "scalar": 10**0}, + -3: {"long_suffix": "milli", "short_suffix": "m", "scalar": 10**-3}, + -6: {"long_suffix": "micro", "short_suffix": "µ", "scalar": 10**-6}, + -9: {"long_suffix": "nano", "short_suffix": "n", "scalar": 10**-9}, + -12: {"long_suffix": "pico", "short_suffix": "p", "scalar": 10**-12}, + -15: {"long_suffix": "femto", "short_suffix": "f", "scalar": 10**-15}, + -18: {"long_suffix": "atto", "short_suffix": "a", "scalar": 10**-18}, + -21: {"long_suffix": "zepto", "short_suffix": "z", "scalar": 10**-21}, + -24: {"long_suffix": "yocto", "short_suffix": "y", "scalar": 10**-24}, } - exponent = int(math.floor(math.log10(abs(val))/3.0)*3) + exponent = int(math.floor(math.log10(abs(val)) / 3.0) * 3) return suffixes.get(exponent, None) + def si_formatter(value): - ''' + """ Return a triple of scaled value, short suffix, long suffix, or None if the value cannot be classified. - ''' + """ classifier = si_classifier(value) if classifier == None: # Don't know how to classify this value return None - scaled = value / classifier['scalar'] - return (scaled, classifier['short_suffix'], classifier['long_suffix']) + scaled = value / classifier["scalar"] + return (scaled, classifier["short_suffix"], classifier["long_suffix"]) -def si_format(value, precision=4, long_form=False, separator=''): - ''' + +def si_format(value, precision=4, long_form=False, separator=""): + """ "SI prefix" formatted string: return a string with the given precision and an appropriate order-of-3-magnitudes suffix, e.g.: si_format(1001.0) => '1.00K' si_format(0.00000000123, long_form=True, separator=' ') => '1.230 nano' - ''' + """ scaled, short_suffix, long_suffix = si_formatter(value) if scaled == None: @@ -58,5 +61,6 @@ def si_format(value, precision=4, long_form=False, separator=''): else: precision = precision - 3 - return '{scaled:.{precision}f}{separator}{suffix}'.format( - scaled=scaled, precision=precision, separator=separator, suffix=suffix) \ No newline at end of file + return "{scaled:.{precision}f}{separator}{suffix}".format( + scaled=scaled, precision=precision, separator=separator, suffix=suffix + ) diff --git a/lnbits/extensions/gerty/templates/gerty/_api_docs.html b/lnbits/extensions/gerty/templates/gerty/_api_docs.html index 889760e19..db1412799 100644 --- a/lnbits/extensions/gerty/templates/gerty/_api_docs.html +++ b/lnbits/extensions/gerty/templates/gerty/_api_docs.html @@ -71,7 +71,8 @@
Curl example
curl -X DELETE {{ request.base_url - }}gerty/api/v1/gertys/<gerty_id> -H "X-Api-Key: <admin_key>" + }}gerty/api/v1/gertys/<gerty_id> -H "X-Api-Key: + <admin_key>" diff --git a/lnbits/extensions/gerty/templates/gerty/gerty.html b/lnbits/extensions/gerty/templates/gerty/gerty.html index e4401fe14..216e57213 100644 --- a/lnbits/extensions/gerty/templates/gerty/gerty.html +++ b/lnbits/extensions/gerty/templates/gerty/gerty.html @@ -1,40 +1,74 @@ -{% extends "public.html" %} {% block toolbar_title %} {{ gerty.name }}{% endblock %}{% block page %} -{% raw %} +{% extends "public.html" %} {% block toolbar_title %} {{ gerty.name }}{% +endblock %}{% block page %} {% raw %}
- - "{{gerty.sats_quote[0].text}}"
~ Satoshi {{gerty.sats_quote[0].date}} -
+ + "{{gerty.sats_quote[0].text}}"
~ Satoshi {{gerty.sats_quote[0].date}} +
- + {{gerty.exchange[0].amount.toFixed(2)}} {{gerty.exchange[0].fiat}} - - - -
-
-
+ + + +
+ +
+
-
-
{{gertywallet.amount}}
{{gertywallet.name}}
-
-
-
+
+
+ {{gertywallet.amount}} +
+
{{gertywallet.name}}
+
+
+
-
+
-

Onchain Stats

- Difficulty Progress Percent - -
- -
-
+

Onchain Stats

+ Difficulty Progress Percent + +
+ +
+
@@ -46,17 +80,14 @@
-

LN Stats

+

LN Stats

-
- {{gerty.ln}} -
+
{{gerty.ln}}
-{% endraw %} -{% endblock %} {% block scripts %} +{% endraw %} {% endblock %} {% block scripts %} + LNbits.utils + .confirmDialog('Are you sure you want to delete this Gerty?') + .onOk(function () { + LNbits.api + .request( + 'DELETE', + '/gerty/api/v1/gerty/' + gertyId, + _.findWhere(self.g.user.wallets, {id: gerty.wallet}).adminkey + ) + .then(function (response) { + self.gertys = _.reject(self.gertys, function (obj) { + return obj.id == gertyId + }) + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }) + }, + exportCSV: function () { + LNbits.utils.exportCSV(this.gertysTable.columns, this.gertys) + } + }, + created: function () { + if (this.g.user.wallets.length) { + this.getGertys() + } + } + }) + +{% endblock %} {% block styles %} + {% endblock %} - -{% block styles %} - -{% endblock %} \ No newline at end of file diff --git a/lnbits/extensions/gerty/views.py b/lnbits/extensions/gerty/views.py index 630cb48bf..e05861691 100644 --- a/lnbits/extensions/gerty/views.py +++ b/lnbits/extensions/gerty/views.py @@ -1,8 +1,10 @@ +import json from http import HTTPStatus from fastapi import Request from fastapi.params import Depends from fastapi.templating import Jinja2Templates +from loguru import logger from starlette.exceptions import HTTPException from starlette.responses import HTMLResponse @@ -14,18 +16,16 @@ from . import gerty_ext, gerty_renderer from .crud import get_gerty from .views_api import api_gerty_json -import json - -from loguru import logger - templates = Jinja2Templates(directory="templates") + @gerty_ext.get("/", response_class=HTMLResponse) async def index(request: Request, user: User = Depends(check_user_exists)): return gerty_renderer().TemplateResponse( "gerty/index.html", {"request": request, "user": user.dict()} ) + @gerty_ext.get("/{gerty_id}", response_class=HTMLResponse) async def display(request: Request, gerty_id): gerty = await get_gerty(gerty_id) @@ -34,4 +34,6 @@ async def display(request: Request, gerty_id): status_code=HTTPStatus.NOT_FOUND, detail="Gerty does not exist." ) gertyData = await api_gerty_json(gerty_id) - return gerty_renderer().TemplateResponse("gerty/gerty.html", {"request": request, "gerty": gertyData}) \ No newline at end of file + return gerty_renderer().TemplateResponse( + "gerty/gerty.html", {"request": request, "gerty": gertyData} + ) diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py index 4151e7a00..d89a557c7 100644 --- a/lnbits/extensions/gerty/views_api.py +++ b/lnbits/extensions/gerty/views_api.py @@ -1,37 +1,35 @@ -import math -from http import HTTPStatus import json -import httpx -import random +import math import os +import random import time from datetime import datetime +from http import HTTPStatus + +import httpx from fastapi import Query from fastapi.params import Depends +from fastapi.templating import Jinja2Templates from lnurl import decode as decode_lnurl from loguru import logger from starlette.exceptions import HTTPException -from lnbits.core.crud import get_wallet_for_key -from lnbits.core.crud import get_user +from lnbits.core.crud import get_user, get_wallet_for_key from lnbits.core.services import create_invoice from lnbits.core.views.api import api_payment, api_wallet from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key -from fastapi.templating import Jinja2Templates - -from .helpers import * - -from . import gerty_ext -from .crud import create_gerty, update_gerty, delete_gerty, get_gerty, get_gertys -from .models import Gerty - from lnbits.utils.exchange_rates import satoshis_amount_as_fiat + from ...settings import LNBITS_PATH +from . import gerty_ext +from .crud import create_gerty, delete_gerty, get_gerty, get_gertys, update_gerty +from .helpers import * +from .models import Gerty @gerty_ext.get("/api/v1/gerty", status_code=HTTPStatus.OK) async def api_gertys( - all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type) + all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type) ): wallet_ids = [wallet.wallet.id] if all_wallets: @@ -43,9 +41,9 @@ async def api_gertys( @gerty_ext.post("/api/v1/gerty", status_code=HTTPStatus.CREATED) @gerty_ext.put("/api/v1/gerty/{gerty_id}", status_code=HTTPStatus.OK) async def api_link_create_or_update( - data: Gerty, - wallet: WalletTypeInfo = Depends(get_key_type), - gerty_id: str = Query(None), + data: Gerty, + wallet: WalletTypeInfo = Depends(get_key_type), + gerty_id: str = Query(None), ): if gerty_id: gerty = await get_gerty(gerty_id) @@ -70,7 +68,7 @@ async def api_link_create_or_update( @gerty_ext.delete("/api/v1/gerty/{gerty_id}") async def api_gerty_delete( - gerty_id: str, wallet: WalletTypeInfo = Depends(require_admin_key) + gerty_id: str, wallet: WalletTypeInfo = Depends(require_admin_key) ): gerty = await get_gerty(gerty_id) @@ -88,10 +86,11 @@ async def api_gerty_delete( ####################### + @gerty_ext.get("/api/v1/gerty/satoshiquote", status_code=HTTPStatus.OK) async def api_gerty_satoshi(): - maxQuoteLength = 186; - with open(os.path.join(LNBITS_PATH, 'extensions/gerty/static/satoshi.json')) as fd: + maxQuoteLength = 186 + with open(os.path.join(LNBITS_PATH, "extensions/gerty/static/satoshi.json")) as fd: satoshiQuotes = json.load(fd) quote = satoshiQuotes[random.randint(0, len(satoshiQuotes) - 1)] # logger.debug(quote.text) @@ -103,10 +102,7 @@ async def api_gerty_satoshi(): @gerty_ext.get("/api/v1/gerty/{gerty_id}/{p}") -async def api_gerty_json( - gerty_id: str, - p: int = None # page number -): +async def api_gerty_json(gerty_id: str, p: int = None): # page number gerty = await get_gerty(gerty_id) if not gerty: @@ -129,7 +125,7 @@ async def api_gerty_json( logger.debug("Screeens " + str(enabled_screens)) data = await get_screen_data(p, enabled_screens, gerty) - next_screen_number = 0 if ((p + 1) >= enabled_screen_count) else p + 1; + next_screen_number = 0 if ((p + 1) >= enabled_screen_count) else p + 1 # get the sleep time sleep_time = gerty.refresh_time if gerty.refresh_time else 300 @@ -143,14 +139,14 @@ async def api_gerty_json( "requestTimestamp": get_next_update_time(sleep_time), "nextScreenNumber": next_screen_number, "showTextBoundRect": False, - "name": gerty.name + "name": gerty.name, }, "screen": { "slug": get_screen_slug_by_index(p, enabled_screens), "group": get_screen_slug_by_index(p, enabled_screens), - "title": data['title'], - "areas": data['areas'] - } + "title": data["title"], + "areas": data["areas"], + }, } @@ -163,7 +159,7 @@ def get_screen_slug_by_index(index: int, screens_list): async def get_screen_data(screen_num: int, screens_list: dict, gerty): screen_slug = get_screen_slug_by_index(screen_num, screens_list) # first get the relevant slug from the display_preferences - logger.debug('screen_slug') + logger.debug("screen_slug") logger.debug(screen_slug) areas = [] title = "" @@ -188,8 +184,8 @@ async def get_screen_data(screen_num: int, screens_list: dict, gerty): areas = await get_lightning_stats(gerty) data = {} - data['title'] = title - data['areas'] = areas + data["title"] = title + data["areas"] = areas return data @@ -208,8 +204,10 @@ async def get_dashboard(gerty): wallets = await get_lnbits_wallet_balances(gerty) text = [] for wallet in wallets: - text.append(get_text_item_dict("{0}".format(wallet['name']), 15)) - text.append(get_text_item_dict("{0} sats".format(format_number(wallet['balance'])), 20)) + text.append(get_text_item_dict("{0}".format(wallet["name"]), 15)) + text.append( + get_text_item_dict("{0} sats".format(format_number(wallet["balance"])), 20) + ) areas.append(text) # Mempool fees @@ -220,7 +218,11 @@ async def get_dashboard(gerty): # difficulty adjustment time text = [] - text.append(get_text_item_dict(await get_time_remaining_next_difficulty_adjustment(gerty), 15)) + text.append( + get_text_item_dict( + await get_time_remaining_next_difficulty_adjustment(gerty), 15 + ) + ) text.append(get_text_item_dict("until next difficulty adjustment", 12)) areas.append(text) @@ -235,18 +237,20 @@ async def get_lnbits_wallet_balances(gerty): wallet = await get_wallet_for_key(key=lnbits_wallet) logger.debug(wallet.name) if wallet: - wallets.append({ - "name": wallet.name, - "balance": wallet.balance_msat / 1000, - "inkey": wallet.inkey, - }) + wallets.append( + { + "name": wallet.name, + "balance": wallet.balance_msat / 1000, + "inkey": wallet.inkey, + } + ) return wallets async def get_placeholder_text(): return [ get_text_item_dict("Some placeholder text", 15, 10, 50), - get_text_item_dict("Some placeholder text", 15, 10, 50) + get_text_item_dict("Some placeholder text", 15, 10, 50), ] @@ -255,10 +259,12 @@ async def get_satoshi_quotes(): text = [] quote = await api_gerty_satoshi() if quote: - if quote['text']: - text.append(get_text_item_dict(quote['text'], 15)) - if quote['date']: - text.append(get_text_item_dict("Satoshi Nakamoto - {0}".format(quote['date']), 15)) + if quote["text"]: + text.append(get_text_item_dict(quote["text"], 15)) + if quote["date"]: + text.append( + get_text_item_dict("Satoshi Nakamoto - {0}".format(quote["date"]), 15) + ) return text @@ -270,7 +276,11 @@ async def get_exchange_rate(gerty): amount = await satoshis_amount_as_fiat(100000000, gerty.exchange) if amount: price = format_number(amount) - text.append(get_text_item_dict("Current {0}/BTC price".format(gerty.exchange), 15)) + text.append( + get_text_item_dict( + "Current {0}/BTC price".format(gerty.exchange), 15 + ) + ) text.append(get_text_item_dict(price, 80)) except: pass @@ -281,29 +291,43 @@ async def get_onchain_dashboard(gerty): areas = [] if isinstance(gerty.mempool_endpoint, str): async with httpx.AsyncClient() as client: - r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment") + r = await client.get( + gerty.mempool_endpoint + "/api/v1/difficulty-adjustment" + ) text = [] - stat = round(r.json()['progressPercent']) - text.append(get_text_item_dict("Progress through current difficulty epoch", 12)) + stat = round(r.json()["progressPercent"]) + text.append( + get_text_item_dict("Progress through current difficulty epoch", 12) + ) text.append(get_text_item_dict("{0}%".format(stat), 20)) areas.append(text) text = [] - stat = r.json()['estimatedRetargetDate'] + stat = r.json()["estimatedRetargetDate"] dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M") - text.append(get_text_item_dict("Estimated date of next difficulty adjustment", 12)) + text.append( + get_text_item_dict("Estimated date of next difficulty adjustment", 12) + ) text.append(get_text_item_dict(dt, 20)) areas.append(text) text = [] - stat = r.json()['remainingBlocks'] - text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 12)) + stat = r.json()["remainingBlocks"] + text.append( + get_text_item_dict( + "Blocks remaining until next difficulty adjustment", 12 + ) + ) text.append(get_text_item_dict("{0}".format(format_number(stat)), 20)) areas.append(text) text = [] - stat = r.json()['remainingTime'] - text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 12)) + stat = r.json()["remainingTime"] + text.append( + get_text_item_dict( + "Blocks remaining until next difficulty adjustment", 12 + ) + ) text.append(get_text_item_dict(get_time_remaining(stat / 1000, 4), 20)) areas.append(text) @@ -313,8 +337,10 @@ async def get_onchain_dashboard(gerty): async def get_time_remaining_next_difficulty_adjustment(gerty): if isinstance(gerty.mempool_endpoint, str): async with httpx.AsyncClient() as client: - r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment") - stat = r.json()['remainingTime'] + r = await client.get( + gerty.mempool_endpoint + "/api/v1/difficulty-adjustment" + ) + stat = r.json()["remainingTime"] time = get_time_remaining(stat / 1000, 3) return time @@ -331,17 +357,15 @@ async def get_mempool_stat(stat_slug: str, gerty): text = [] if isinstance(gerty.mempool_endpoint, str): async with httpx.AsyncClient() as client: - if ( - stat_slug == "mempool_tx_count" - ): + if stat_slug == "mempool_tx_count": r = await client.get(gerty.mempool_endpoint + "/api/mempool") if stat_slug == "mempool_tx_count": - stat = round(r.json()['count']) + stat = round(r.json()["count"]) text.append(get_text_item_dict("Transactions in the mempool", 15)) - text.append(get_text_item_dict("{0}".format(format_number(stat)), 80)) - elif ( - stat_slug == "mempool_recommended_fees" - ): + text.append( + get_text_item_dict("{0}".format(format_number(stat)), 80) + ) + elif stat_slug == "mempool_recommended_fees": y_offset = 60 fees = await get_mempool_recommended_fees(gerty) pos_y = 80 + y_offset @@ -350,35 +374,75 @@ async def get_mempool_stat(stat_slug: str, gerty): text.append(get_text_item_dict("Recommended Tx Fees", 20, 240, pos_y)) pos_y = 280 + y_offset - text.append(get_text_item_dict("{0}".format("No Priority"), 15, 30, pos_y)) - text.append(get_text_item_dict("{0}".format("Low Priority"), 15, 235, pos_y)) - text.append(get_text_item_dict("{0}".format("Medium Priority"), 15, 460, pos_y)) - text.append(get_text_item_dict("{0}".format("High Priority"), 15, 750, pos_y)) + text.append( + get_text_item_dict("{0}".format("No Priority"), 15, 30, pos_y) + ) + text.append( + get_text_item_dict("{0}".format("Low Priority"), 15, 235, pos_y) + ) + text.append( + get_text_item_dict("{0}".format("Medium Priority"), 15, 460, pos_y) + ) + text.append( + get_text_item_dict("{0}".format("High Priority"), 15, 750, pos_y) + ) pos_y = 340 + y_offset font_size = 15 fee_append = "/vB" fee_rate = fees["economyFee"] - text.append(get_text_item_dict( - "{0} {1}{2}".format(format_number(fee_rate), ("sat" if fee_rate == 1 else "sats"), fee_append), - font_size, - 30, pos_y)) + text.append( + get_text_item_dict( + "{0} {1}{2}".format( + format_number(fee_rate), + ("sat" if fee_rate == 1 else "sats"), + fee_append, + ), + font_size, + 30, + pos_y, + ) + ) fee_rate = fees["hourFee"] - text.append(get_text_item_dict( - "{0} {1}{2}".format(format_number(fee_rate), ("sat" if fee_rate == 1 else "sats"), fee_append), - font_size, - 235, pos_y)) + text.append( + get_text_item_dict( + "{0} {1}{2}".format( + format_number(fee_rate), + ("sat" if fee_rate == 1 else "sats"), + fee_append, + ), + font_size, + 235, + pos_y, + ) + ) fee_rate = fees["halfHourFee"] - text.append(get_text_item_dict( - "{0} {1}{2}".format(format_number(fee_rate), ("sat" if fee_rate == 1 else "sats"), fee_append), - font_size, - 460, pos_y)) + text.append( + get_text_item_dict( + "{0} {1}{2}".format( + format_number(fee_rate), + ("sat" if fee_rate == 1 else "sats"), + fee_append, + ), + font_size, + 460, + pos_y, + ) + ) fee_rate = fees["fastestFee"] - text.append(get_text_item_dict( - "{0} {1}{2}".format(format_number(fee_rate), ("sat" if fee_rate == 1 else "sats"), fee_append), - font_size, - 750, pos_y)) + text.append( + get_text_item_dict( + "{0} {1}{2}".format( + format_number(fee_rate), + ("sat" if fee_rate == 1 else "sats"), + fee_append, + ), + font_size, + 750, + pos_y, + ) + ) return text diff --git a/tests/core/views/test_api.py b/tests/core/views/test_api.py index 81468fd1f..c62801535 100644 --- a/tests/core/views/test_api.py +++ b/tests/core/views/test_api.py @@ -3,10 +3,9 @@ import hashlib import pytest from lnbits import bolt11 -from lnbits.core.views.api import ( - api_payment, -) +from lnbits.core.views.api import api_payment from lnbits.settings import wallet_class + from ...helpers import get_random_invoice_data, is_regtest diff --git a/tests/extensions/boltz/conftest.py b/tests/extensions/boltz/conftest.py index 1bd1c638d..930a1bfb1 100644 --- a/tests/extensions/boltz/conftest.py +++ b/tests/extensions/boltz/conftest.py @@ -1,9 +1,7 @@ import pytest_asyncio from lnbits.extensions.boltz.boltz import create_reverse_swap -from lnbits.extensions.boltz.models import ( - CreateReverseSubmarineSwap, -) +from lnbits.extensions.boltz.models import CreateReverseSubmarineSwap @pytest_asyncio.fixture(scope="session") diff --git a/tests/mocks.py b/tests/mocks.py index 7e2df4f70..3fc0efae2 100644 --- a/tests/mocks.py +++ b/tests/mocks.py @@ -4,6 +4,7 @@ from lnbits import bolt11 from lnbits.settings import WALLET from lnbits.wallets.base import PaymentResponse, PaymentStatus, StatusResponse from lnbits.wallets.fake import FakeWallet + from .helpers import get_random_string, is_fake