Formatted gerty extension

This commit is contained in:
Black Coffee 2022-10-20 16:31:09 +01:00
parent 1c56622d89
commit 07042a7d4f
17 changed files with 988 additions and 827 deletions

View file

@ -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,

View file

@ -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"])

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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])

View file

@ -15,4 +15,4 @@ async def m001_initial(db):
display_preferences TEXT
);
"""
)
)

View file

@ -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))
return cls(**dict(row))

View file

@ -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)
return "{scaled:.{precision}f}{separator}{suffix}".format(
scaled=scaled, precision=precision, separator=separator, suffix=suffix
)

View file

@ -71,7 +71,8 @@
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X DELETE {{ request.base_url
}}gerty/api/v1/gertys/&lt;gerty_id&gt; -H "X-Api-Key: &lt;admin_key&gt;"
}}gerty/api/v1/gertys/&lt;gerty_id&gt; -H "X-Api-Key:
&lt;admin_key&gt;"
</code>
</q-card-section>
</q-card>

View file

@ -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 %}
<div class="q-pa-md row items-start q-gutter-md">
<q-card unelevated flat class="q-pa-none text-body1 blockquote" style="background: none !important;">
"{{gerty.sats_quote[0].text}}" <br/>~ Satoshi {{gerty.sats_quote[0].date}}
</q-card>
<q-card
unelevated
flat
class="q-pa-none text-body1 blockquote"
style="background: none !important"
>
"{{gerty.sats_quote[0].text}}" <br />~ Satoshi {{gerty.sats_quote[0].date}}
</q-card>
</div>
<div class="q-pa-md row items-start q-gutter-md">
<q-card unelevated flat class="q-pa-none" style="background: none !important;">
<q-card unelevated flat class="q-pa-none" style="background: none !important">
<q-card-section class="text-h1 q-pa-none">
{{gerty.exchange[0].amount.toFixed(2)}} {{gerty.exchange[0].fiat}}
</q-card-section>
</q-card>
<q-card v-for="gertywallet in gertywallets" style="width:380px" flat>
<q-card-section horizontal class="q-pa-none" :style="`background-color: ${gertywallet.color1}`">
<q-card-section class="q-pa-none">
<div class="q-item__section column q-pa-lg q-mr-none text-white q-item__section--side justify-center" :style="`background-color: ${gertywallet.color2}`" >
<i aria-hidden="true" role="presentation" class="material-icons q-icon notranslate text-white" style="font-size: 50px;">sentiment_satisfied</i></div>
</q-card-section>
<q-card v-for="gertywallet in gertywallets" style="width: 380px" flat>
<q-card-section
horizontal
class="q-pa-none"
:style="`background-color: ${gertywallet.color1}`"
>
<q-card-section class="q-pa-none">
<div
class="q-item__section column q-pa-lg q-mr-none text-white q-item__section--side justify-center"
:style="`background-color: ${gertywallet.color2}`"
>
<i
aria-hidden="true"
role="presentation"
class="material-icons q-icon notranslate text-white"
style="font-size: 50px"
>sentiment_satisfied</i
>
</div>
</q-card-section>
<div class="q-item__section column q-pa-md q-ml-none text-white q-item__section--main justify-center" style="min-width:200px;">
<div class="q-item__label text-white text-h6 text-weight-bolder">{{gertywallet.amount}}</div><div class="q-item__label"><b>{{gertywallet.name}}</b></div>
</div>
</q-card-section>
</q-card>
<div
class="q-item__section column q-pa-md q-ml-none text-white q-item__section--main justify-center"
style="min-width: 200px"
>
<div class="q-item__label text-white text-h6 text-weight-bolder">
{{gertywallet.amount}}
</div>
<div class="q-item__label"><b>{{gertywallet.name}}</b></div>
</div>
</q-card-section>
</q-card>
</div>
<div class="row q-col-gutter-md ">
<div class="row q-col-gutter-md">
<div v-if="gerty.onchain[0]" class="col-12 col-sm-6 col-md-5 col-lg-6">
<q-card class="q-pa-lg">
<p class="text-h4">Onchain Stats</p>
Difficulty Progress Percent
<q-linear-progress size="20px" :value="gerty.onchain[0].difficulty[0].progressPercent/100" color="primary" class="q-mt-sm">
<div class="absolute-full flex flex-center">
<q-badge color="white" text-color="accent" :label="gerty.onchain[0].difficulty[0].progressPercent.toFixed() + '%'" />
</div>
</q-linear-progress>
<p class="text-h4">Onchain Stats</p>
Difficulty Progress Percent
<q-linear-progress
size="20px"
:value="gerty.onchain[0].difficulty[0].progressPercent/100"
color="primary"
class="q-mt-sm"
>
<div class="absolute-full flex flex-center">
<q-badge
color="white"
text-color="accent"
:label="gerty.onchain[0].difficulty[0].progressPercent.toFixed() + '%'"
/>
</div>
</q-linear-progress>
<q-card-section class="q-pa-none">
<div class="row q-mt-lg q-gutter-sm text-h6">
@ -46,17 +80,14 @@
</div>
<div v-if="gerty.ln[0]" class="col-12 col-sm-6 col-md-5 col-lg-6">
<q-card class="q-pa-lg">
<p class="text-h4">LN Stats</p>
<p class="text-h4">LN Stats</p>
<q-card-section class="q-pa-none">
<div class="row q-mt-lg q-gutter-sm">
{{gerty.ln}}
</div>
<div class="row q-mt-lg q-gutter-sm">{{gerty.ln}}</div>
</q-card-section>
</q-card>
</div>
</div>
{% endraw %}
{% endblock %} {% block scripts %}
{% endraw %} {% endblock %} {% block scripts %}
<script>
Vue.component(VueQrcode.name, VueQrcode)

File diff suppressed because it is too large Load diff

View file

@ -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})
return gerty_renderer().TemplateResponse(
"gerty/gerty.html", {"request": request, "gerty": gertyData}
)

View file

@ -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

View file

@ -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

View file

@ -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")

View file

@ -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