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 -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 %}
Onchain Stats
- Difficulty Progress Percent -Onchain Stats
+ Difficulty Progress Percent +LN Stats
+LN Stats