Merge pull request #1018 from blackcoffeexbt/gerty

Gerty
This commit is contained in:
Arc 2022-09-30 09:03:36 +01:00 committed by GitHub
commit e084495814
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 1115 additions and 628 deletions

62
lnbits/Pipfile Normal file
View file

@ -0,0 +1,62 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
aiofiles = "==0.8.0"
anyio = "==3.6.1"
asyncio = "==3.4.3"
attrs = "==21.4.0"
bech32 = "==1.2.0"
bitstring = "==3.1.9"
cerberus = "==1.3.4"
certifi = "==2022.6.15"
cffi = "==1.15.0"
click = "==8.1.3"
ecdsa = "==0.18.0"
embit = "==0.5.0"
environs = "==9.5.0"
fastapi = "==0.79.0"
h11 = "==0.12.0"
httpcore = "==0.15.0"
httptools = "==0.4.0"
httpx = "==0.23.0"
idna = "==3.3"
jinja2 = "==3.0.1"
lnurl = "==0.3.6"
loguru = "==0.6.0"
markupsafe = "==2.1.1"
marshmallow = "==3.17.0"
outcome = "==1.2.0"
psycopg2-binary = "==2.9.3"
pycparser = "==2.21"
pycryptodomex = "==3.15.0"
pydantic = "==1.9.1"
pyngrok = "==5.1.0"
pyparsing = "==3.0.9"
pypng = "==0.20220715.0"
pyqrcode = "==1.2.1"
pyscss = "==1.4.0"
python-dotenv = "==0.20.0"
pyyaml = "==6.0"
represent = "==1.6.0.post0"
rfc3986 = "==1.5.0"
secp256k1 = "==0.14.0"
shortuuid = "==1.0.9"
six = "==1.16.0"
sniffio = "==1.2.0"
sqlalchemy-aio = "==0.17.0"
sqlalchemy = "==1.3.23"
sse-starlette = "==0.10.3"
starlette = "==0.19.1"
typing-extensions = "==4.3.0"
uvicorn = "==0.18.2"
uvloop = "==0.16.0"
watchfiles = "==0.16.0"
websockets = "==10.3"
[dev-packages]
[requires]
python_version = "3.9"

View file

@ -2,5 +2,5 @@
"name": "Gerty",
"short_description": "Desktop bitcoin Assistant",
"icon": "sentiment_satisfied",
"contributors": ["arcbtc"]
"contributors": ["arcbtc", "blackcoffeebtc"]
}

View file

@ -10,8 +10,16 @@ async def create_gerty(wallet_id: str, data: Gerty) -> Gerty:
gerty_id = urlsafe_short_hash()
await db.execute(
"""
INSERT INTO gerty.gertys (id, name, wallet, lnbits_wallets, mempool_endpoint, sats_quote, exchange, onchain_stats, ln_stats)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
INSERT INTO gerty.gertys (
id,
name,
wallet,
lnbits_wallets,
mempool_endpoint,
exchange,
display_preferences
)
VALUES (?, ?, ?, ?, ?, ?, ?)
""",
(
gerty_id,
@ -19,10 +27,8 @@ async def create_gerty(wallet_id: str, data: Gerty) -> Gerty:
data.wallet,
data.lnbits_wallets,
data.mempool_endpoint,
data.sats_quote,
data.exchange,
data.onchain_stats,
data.ln_stats,
data.display_preferences
),
)

View file

@ -1,19 +1,18 @@
async def m001_initial(db):
"""
Initial gertys table.
Initial Gertys table.
"""
await db.execute(
"""
CREATE TABLE gerty.gertys (
id TEXT PRIMARY KEY,
refresh_time INT,
name TEXT NOT NULL,
wallet TEXT NOT NULL,
lnbits_wallets TEXT,
mempool_endpoint TEXT,
sats_quote BOOL,
exchange TEXT,
onchain_stats BOOL,
ln_stats BOOL
display_preferences TEXT
);
"""
)

View file

@ -4,18 +4,15 @@ 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
sats_quote: bool = Query(False) # Fetch Satoshi quotes
exchange: str = Query(None) # BTC <-> Fiat exchange rate to pull ie "USD", in 0.0001 and sats
onchain_stats: bool = Query(False) # Onchain stats
ln_stats: bool = Query(False) # ln Sats
display_preferences: str = Query(None)
@classmethod
def from_row(cls, row: Row) -> "Gerty":

View file

@ -0,0 +1,14 @@
{
"facts": [
"When a woman asked Pieter Wuille to talk dirty to her, he described the OpenSSL DER implementation.",
"Pieter Wuille recently visited an event horizon and escaped with a cryptographic proof.",
"Pieter Wuille's PhD thesis defence in full: \"Pieter Wuille, thank you\".",
"Pieter Wuille is an acronym for Programmatic Intelligent Encrypted Telemetric Encapsulated Recursive Witness Upscaling Integrated Load-Balancing Logical Entity.",
"Dan Bernstein only trusts one source of random numbers: Pieter Wuille.",
"Putting Pieter Wuille in the title of an r/Bitcoin submission gets more upvotes than the same post from Pieter Wuille himself.",
"Pieter Wuille won the underhanded crypto contest but his entry was so underhanded nobody even knows he entered.",
"Greg Maxwell is a bot created by Pieter Wuille to argue on reddit while he can get code done.",
"Pieter Wuille doesn't need the public key to calculate the corresponding private key.",
"When the Wikipedia servers corrupted all data including backups, Pieter Wuille had to stay awake all night to retype it."
]
}

View file

@ -1,11 +1,11 @@
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %}
<div class="row q-col-gutter-md">
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context %} {% block page %}
<div class="row q-col-gutter-md">
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
<q-card>
<q-card-section>
<q-btn unelevated color="primary" @click="formDialog.show = true"
>New Gerty</q-btn
>New Gerty
</q-btn
>
</q-card-section>
</q-card>
@ -31,7 +31,8 @@
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
<q-th v-for="col in props.cols" :key="col.name" :props="props"
:class="`col__${col.name} text-truncate elipsis`">
{{ col.label }}
</q-th>
<q-th auto-width></q-th>
@ -51,7 +52,9 @@
type="a"
:href="props.row.gerty"
target="_blank"
><q-tooltip>Launch software Gerty</q-tooltip></q-btn>
>
<q-tooltip>Launch software Gerty</q-tooltip>
</q-btn>
<q-btn
unelevated
dense
@ -61,7 +64,9 @@
type="a"
:href="props.row.gertyJson"
target="_blank"
><q-tooltip>Launch software Gerty</q-tooltip></q-btn>
>
<q-tooltip>Launch software Gerty</q-tooltip>
</q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ (col.name == 'tip_options' && col.value ?
@ -98,7 +103,7 @@
<div class="col-12 col-md-5 q-gutter-y-md">
<q-card>
<q-card-section>
<h6 class="text-subtitle1 q-my-none">{{SITE_TITLE}} Gerty extension</h6>
<h6 class="text-subtitle1 q-my-none">{{ SITE_TITLE }} Gerty extension</h6>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
@ -139,7 +144,9 @@
hide-dropdown-icon
new-value-mode="add-unique"
label="Invoice keys of wallets to watch"
><q-tooltip>Hit enter to add values</q-tooltip></q-select>
>
<q-tooltip>Hit enter to add values</q-tooltip>
</q-select>
<q-select
filled
dense
@ -153,19 +160,182 @@
dense
v-model.trim="formDialog.data.mempool_endpoint"
label="Mempool link"
><q-tooltip>Used for getting onchain/ln stats</q-tooltip></q-input>
>
<q-tooltip>Used for getting onchain/ln stats</q-tooltip>
</q-input>
<q-input
filled
dense
v-model.trim="formDialog.data.refresh_time"
label="Refresh time in seconds"
>
<q-tooltip>The amount of time in seconds between screen updates</q-tooltip>
</q-input>
<p>Use the toggles below to control what your Gerty will display</p>
<q-expansion-item
expand-separator
icon="perm_identity"
label="LNbits Wallets"
>
<q-toggle
v-model="formDialog.data.sats_quote"
v-model="formDialog.data.display_preferences.lnbits_wallets_balance"
label="Show LNbits wallet balances"
></q-toggle>
</q-expansion-item>
<q-expansion-item
expand-separator
icon="celebration"
label="The Fun Stuff"
>
<q-toggle
v-model="toggleStates.fun"
label="Toggle all"
>
<q-tooltip>Toggle all</q-tooltip>
</q-toggle>
<br>
<q-toggle
v-model="formDialog.data.display_preferences.fun_satoshi_quotes"
label="Satoshi Quotes"
><q-tooltip>Gets random quotes from satoshi</q-tooltip></q-toggle>
>
<q-tooltip>Displays random quotes from Satoshi</q-tooltip>
</q-toggle>
<q-toggle
v-model="formDialog.data.onchain_stats"
label="Onchain Statistics"
><q-tooltip>Gets Onchain Statistics</q-tooltip></q-toggle>
v-model="formDialog.data.display_preferences.fun_pieter_wuille_facts"
label="Pieter Wuille Facts"
>
<q-tooltip>Show accurate facts about Pieter Wuille</q-tooltip>
</q-toggle>
<q-toggle
v-model="formDialog.data.ln_stats"
label="LN Statistics"
><q-tooltip>Gets Lightning-Network Statistics</q-tooltip></q-toggle>
v-model="formDialog.data.display_preferences.fun_exchange_market_rate"
label="Current dirty fiat to BTC price"
></q-toggle>
</q-expansion-item>
<q-expansion-item
expand-separator
icon="link"
label="Onchain Information"
>
<q-toggle
v-model="toggleStates.onchain"
label="Toggle all"
>
<q-tooltip>Toggle all</q-tooltip>
</q-toggle>
<br>
<q-toggle
v-model="formDialog.data.display_preferences.onchain_difficulty_epoch_progress"
label="Percent of current difficulty epoch complete"
></q-toggle>
<q-toggle
v-model="formDialog.data.display_preferences.onchain_difficulty_retarget_date"
label="Estimated retarget date"
></q-toggle>
<q-toggle
v-model="formDialog.data.display_preferences.onchain_difficulty_blocks_remaining"
label="Blocks until next difficulty adjustment"
></q-toggle>
<q-toggle
v-model="formDialog.data.display_preferences.onchain_difficulty_epoch_time_remaining"
label="Estimated time until next difficulty adjustment"
></q-toggle>
</q-expansion-item>
<q-expansion-item
expand-separator
icon="psychology"
label="The Mempool"
>
<q-toggle
v-model="toggleStates.mempool"
label="Toggle all"
>
<q-tooltip>Toggle all</q-tooltip>
</q-toggle>
<br>
<q-toggle
v-model="formDialog.data.display_preferences.mempool_recommended_fees"
label="Recommended fees"
></q-toggle>
<q-toggle
v-model="formDialog.data.display_preferences.mempool_tx_count"
label="Number of transactions in the mempool"
></q-toggle>
</q-expansion-item>
<q-expansion-item
expand-separator
icon="money"
label="Mining Data"
>
<q-toggle
v-model="toggleStates.mining"
label="Toggle all"
>
<q-tooltip>Toggle all</q-tooltip>
</q-toggle>
<br>
<q-toggle
v-model="formDialog.data.display_preferences.mining_current_hash_rate"
label="Current mining hashrate"
></q-toggle>
<q-toggle
v-model="formDialog.data.display_preferences.mining_current_difficulty"
label="Current mining difficulty"
></q-toggle>
</q-expansion-item>
<q-expansion-item
expand-separator
icon="bolt"
label="Lightning Network"
>
<q-toggle
v-model="toggleStates.lightning"
label="Toggle all"
>
<q-tooltip>Toggle all</q-tooltip>
</q-toggle>
<br>
<q-toggle
v-model="formDialog.data.display_preferences.lightning_channel_count"
label="Channel count"
></q-toggle>
<q-toggle
v-model="formDialog.data.display_preferences.lightning_node_count"
label="Number of nodes"
></q-toggle>
<q-toggle
v-model="formDialog.data.display_preferences.lightning_tor_node_count"
label="Number of Tor nodes"
></q-toggle>
<q-toggle
v-model="formDialog.data.display_preferences.lightning_clearnet_nodes"
label="Number of clearnet nodes"
></q-toggle>
<q-toggle
v-model="formDialog.data.display_preferences.lightning_unannounced_nodes"
label="Number of unannounced nodes"
></q-toggle>
<q-toggle
v-model="formDialog.data.display_preferences.lightning_average_channel_capacity"
label="Average channel capacity"
></q-toggle>
</q-expansion-item>
<div class="row q-mt-lg">
<q-btn
unelevated
@ -173,7 +343,8 @@
:disable="formDialog.data.wallet == null || formDialog.data.name == null"
type="submit"
class="q-mr-md"
>Create Gerty</q-btn
>Create Gerty
</q-btn
>
<q-btn
v-if="formDialog.data.id"
@ -181,18 +352,20 @@
color="primary"
:disable="formDialog.data.wallet == null || formDialog.data.name == null"
type="submit"
>Update Gerty</q-btn
>Update Gerty
</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>Cancel
</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
</div>
</div>
{% endblock %} {% block scripts %} {{ window_vars(user) }}
<script>
<script>
var mapGerty = function (obj) {
obj.date = Quasar.utils.date.formatDate(
new Date(obj.time * 1000),
@ -209,6 +382,14 @@
mixins: [windowMixin],
data: function () {
return {
toggleStates: {
fun: true,
onchain: true,
mempool: true,
mining: true,
lightning: true
},
oldToggleStates: {},
gertys: [],
currencyOptions: [
'USD',
@ -398,31 +579,13 @@
name: 'exchange',
align: 'left',
label: 'Exchange',
field: 'exchange'
},
{
name: 'sats_quote',
align: 'left',
label: 'Sats Quote',
field: 'sats_quote'
field: 'exchange',
},
{
name: 'mempool_endpoint',
align: 'left',
label: 'Mempool Endpoint',
field: 'mempool_endpoint'
},
{
name: 'onchain_stats',
align: 'left',
label: 'Onchain Stats',
field: 'onchain_stats'
},
{
name: 'ln_stats',
align: 'left',
label: 'LN Stats',
field: 'ln_stats'
}
],
pagination: {
@ -431,21 +594,46 @@
},
formDialog: {
show: false,
data: {sats_quote: false,
onchain_stats: false,
ln_stats: false,
lnbits_wallets:[],
mempool_endpoint: "https://mempool.space"}
data: {
display_preferences: {
lnbits_wallets_balance: true,
fun_satoshi_quotes: true,
fun_pieter_wuille_facts: true,
fun_exchange_market_rate: true,
onchain_difficulty_epoch_progress: true,
onchain_difficulty_retarget_date: true,
onchain_difficulty_blocks_remaining: true,
onchain_difficulty_epoch_time_remaining: true,
mempool_recommended_fees: true,
mempool_tx_count: true,
mining_current_hash_rate: true,
mining_current_difficulty: true,
lightning_channel_count: true,
lightning_node_count: true,
lightning_tor_node_count: true,
lightning_clearnet_nodes: true,
lightning_unannounced_nodes: true,
lightning_average_channel_capacity: true,
},
lnbits_wallets: [],
mempool_endpoint: "https://mempool.space",
refresh_time: 300,
}
}
}
},
mounted() {
console.log('this.formDialog', this.formDialog.data.display_preferences)
},
methods: {
closeFormDialog: function () {
this.formDialog.data = {sats_quote: false,
onchain_stats: false,
ln_stats: false,
lnbits_wallets:[],
mempool_endpoint: "https://mempool.space"}
this.formDialog.data = {
lnbits_wallets: [],
mempool_endpoint: "https://mempool.space",
refresh_time: 300,
display_preferences: {},
}
},
getGertys: function () {
var self = this
@ -463,28 +651,28 @@
},
updateformDialog: function (formId) {
var gerty = _.findWhere(this.gertys, {id: formId})
console.log('gerty.display_preferences', gerty.display_preferences)
this.formDialog.data.id = gerty.id
this.formDialog.data.name = gerty.name
this.formDialog.data.wallet = gerty.wallet
this.formDialog.data.lnbits_wallets = JSON.parse(gerty.lnbits_wallets)
this.formDialog.data.exchange = gerty.exchange,
this.formDialog.data.mempool_endpoint = gerty.mempool_endpoint,
this.formDialog.data.sats_quote = Boolean(gerty.sats_quote)
this.formDialog.data.onchain_stats = Boolean(gerty.onchain_stats)
this.formDialog.data.ln_stats = Boolean(gerty.ln_stats)
this.formDialog.data.refresh_time = gerty.refresh_time,
this.formDialog.data.display_preferences = JSON.parse(gerty.display_preferences),
this.formDialog.show = true
},
sendFormDataGerty: function () {
var self = this
if (self.formDialog.data.id) {
if (this.formDialog.data.id) {
this.updateGerty(
self.g.user.wallets[0].adminkey,
self.formDialog.data
this.g.user.wallets[0].adminkey,
this.formDialog.data
)
} else {
{#console.log('sendFormDataGerty', this.formDialog.data)#}
this.createGerty(
self.g.user.wallets[0].adminkey,
self.formDialog.data
this.g.user.wallets[0].adminkey,
this.formDialog.data
)
}
},
@ -493,12 +681,12 @@
name: this.formDialog.data.name,
wallet: this.formDialog.data.wallet,
lnbits_wallets: JSON.stringify(this.formDialog.data.lnbits_wallets),
sats_quote: this.formDialog.data.sats_quote,
exchange: this.formDialog.data.exchange,
mempool_endpoint: this.formDialog.data.mempool_endpoint,
onchain_stats: this.formDialog.data.onchain_stats,
ln_stats: this.formDialog.data.ln_stats
refresh_time: this.formDialog.data.refresh_time,
display_preferences: JSON.stringify(this.formDialog.data.display_preferences)
}
console.log('createGerty', data)
var self = this
LNbits.api
@ -520,6 +708,7 @@
updateGerty: function (wallet, data) {
var self = this
data.lnbits_wallets = JSON.stringify(this.formDialog.data.lnbits_wallets)
data.display_preferences = JSON.stringify(this.formDialog.data.display_preferences)
LNbits.api
.request(
'PUT',
@ -569,7 +758,36 @@
if (this.g.user.wallets.length) {
this.getGertys()
}
},
watch: {
toggleStates: {
handler(toggleStatesValue) {
// Switch all the toggles in each section to the relevant state
for (const [toggleKey, toggleValue] of Object.entries(toggleStatesValue)) {
if (this.oldToggleStates[toggleKey] !== toggleValue) {
for (const [dpKey, dpValue] of Object.entries(this.formDialog.data.display_preferences)) {
if (dpKey.indexOf(toggleKey) === 0) {
this.formDialog.data.display_preferences[dpKey] = toggleValue
}
}
}
}
// This is a weird hack we have to use to get VueJS to persist the previous toggle state between
// watches. VueJS passes the old and new values by reference so when comparing objects they
// will have the same values unless we do this
this.oldToggleStates = JSON.parse(JSON.stringify(toggleStatesValue))
},
deep: true
}
}
})
</script>
</script>
{% endblock %}
{% block styles %}
<style>
.col__display_preferences {
border: 1px solid red
}
</style>
{% endblock %}

View file

@ -1,15 +1,17 @@
import math
from http import HTTPStatus
import json
import httpx
import random
import os
import time
from datetime import datetime
from fastapi import Query
from fastapi.params import Depends
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.services import create_invoice
@ -63,6 +65,7 @@ async def api_link_create_or_update(
return {**gerty.dict()}
@gerty_ext.delete("/api/v1/gerty/{gerty_id}")
async def api_gerty_delete(
gerty_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
@ -83,83 +86,271 @@ async def api_gerty_delete(
#######################
with open(os.path.join(LNBITS_PATH, 'extensions/gerty/static/satoshi.json')) as fd:
satoshiQuotes = json.load(fd)
@gerty_ext.get("/api/v1/gerty/satoshiquote", status_code=HTTPStatus.OK)
async def api_gerty_satoshi():
with open(os.path.join(LNBITS_PATH, 'extensions/gerty/static/satoshi.json')) as fd:
satoshiQuotes = json.load(fd)
return satoshiQuotes[random.randint(0, 100)]
@gerty_ext.get("/api/v1/gerty/{gerty_id}")
@gerty_ext.get("/api/v1/gerty/pieterwielliequote", status_code=HTTPStatus.OK)
async def api_gerty_wuille():
with open(os.path.join(LNBITS_PATH, 'extensions/gerty/static/pieter_wuille.json')) as fd:
data = json.load(fd)
return data['facts'][random.randint(0, (len(data['facts']) - 1))]
@gerty_ext.get("/api/v1/gerty/{gerty_id}/{p}")
async def api_gerty_json(
gerty_id: str
gerty_id: str,
p: int = None # page number
):
gerty = await get_gerty(gerty_id)
logger.debug(gerty.wallet)
if not gerty:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Gerty does not exist."
)
gertyReturn = []
display_preferences = json.loads(gerty.display_preferences)
enabled_screen_count = 0
enabled_screens = []
for screen_slug in display_preferences:
is_screen_enabled = display_preferences[screen_slug]
if is_screen_enabled:
enabled_screen_count += 1
enabled_screens.append(screen_slug)
text = await get_screen_text(p, enabled_screens, gerty)
next_screen_number = 0 if ((p + 1) >= enabled_screen_count) else p + 1;
# ln = []
# if gerty.ln_stats and isinstance(gerty.mempool_endpoint, str):
# async with httpx.AsyncClient() as client:
# r = await client.get(gerty.mempool_endpoint + "/api/v1/lightning/statistics/latest")
# if r:
# ln.append(r.json())
return {
"settings": {
"refreshTime": gerty.refresh_time,
"requestTimestamp": round(time.time()),
"nextScreenNumber": next_screen_number,
"showTextBoundRect": True,
"name": gerty.name
},
"screen": {
"slug": get_screen_slug_by_index(p, enabled_screens),
"group": get_screen_slug_by_index(p, enabled_screens),
"text": text
}
}
# Get a screen slug by its position in the screens_list
def get_screen_slug_by_index(index: int, screens_list):
return list(screens_list)[index]
# Get a list of text items for the screen number
async def get_screen_text(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)
# text = []
if screen_slug == "lnbits_wallets_balance":
text = await get_lnbits_wallet_balances(gerty)
elif screen_slug == "fun_satoshi_quotes":
text = await get_satoshi_quotes()
elif screen_slug == "fun_pieter_wuille_facts":
text = await get_pieter_wuille_fact()
elif screen_slug == "fun_exchange_market_rate":
text = await get_exchange_rate(gerty)
elif screen_slug == "onchain_difficulty_epoch_progress":
text = await get_onchain_stat(screen_slug, gerty)
elif screen_slug == "onchain_difficulty_retarget_date":
text = await get_onchain_stat(screen_slug, gerty)
elif screen_slug == "onchain_difficulty_blocks_remaining":
text = await get_onchain_stat(screen_slug, gerty)
elif screen_slug == "onchain_difficulty_epoch_time_remaining":
text = await get_onchain_stat(screen_slug, gerty)
elif screen_slug == "mempool_recommended_fees":
text = await get_placeholder_text()
elif screen_slug == "mempool_tx_count":
text = await get_mempool_stat(screen_slug, gerty)
elif screen_slug == "mining_current_hash_rate":
text = await get_placeholder_text()
elif screen_slug == "mining_current_difficulty":
text = await get_placeholder_text()
elif screen_slug == "lightning_channel_count":
text = await get_placeholder_text()
elif screen_slug == "lightning_node_count":
text = await get_placeholder_text()
elif screen_slug == "lightning_tor_node_count":
text = await get_placeholder_text()
elif screen_slug == "lightning_clearnet_nodes":
text = await get_placeholder_text()
elif screen_slug == "lightning_unannounced_nodes":
text = await get_placeholder_text()
elif screen_slug == "lightning_average_channel_capacity":
text = await get_placeholder_text()
return text
async def get_lnbits_wallet_balances(gerty):
# Get Wallet info
wallets = []
text = []
if gerty.lnbits_wallets != "":
for lnbits_wallet in json.loads(gerty.lnbits_wallets):
wallet = await get_wallet_for_key(key=lnbits_wallet)
logger.debug(wallet)
if wallet:
wallets.append({
"name": wallet.name,
"balance": wallet.balance_msat,
"inkey": wallet.inkey,
})
text.append(get_text_item_dict(wallet.name, 20))
text.append(get_text_item_dict(wallet.balance, 40))
return text
#Get Satoshi quotes
satoshi = []
if gerty.sats_quote:
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)
]
async def get_satoshi_quotes():
# Get Satoshi quotes
text = []
quote = await api_gerty_satoshi()
if quote:
satoshi.append(await api_gerty_satoshi())
if quote['text']:
text.append(get_text_item_dict(quote['text'], 15))
if quote['date']:
text.append(get_text_item_dict(quote['date'], 15))
return text
#Get Exchange Value
exchange = []
async def get_pieter_wuille_fact():
text = []
quote = await api_gerty_wuille()
if quote:
text.append(get_text_item_dict(quote, 15))
text.append(get_text_item_dict("Pieter Wuille facts", 15))
return text
# Get Exchange Value
async def get_exchange_rate(gerty):
text = []
if gerty.exchange != "":
try:
amount = await satoshis_amount_as_fiat(100000000, gerty.exchange)
if amount:
exchange.append({
"fiat": gerty.exchange,
"amount": amount,
})
price = ('{0} {1}').format(format_number(amount), gerty.exchange)
text.append(get_text_item_dict("Current BTC price", 15))
text.append(get_text_item_dict(price, 80))
except:
pass
return text
onchain = []
if gerty.onchain_stats and isinstance(gerty.mempool_endpoint, str):
# 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):
text = {
"value": text,
"size": font_size
}
if x_pos is None and y_pos is None:
text['position'] = 'center'
else:
text['x'] = x_pos
text['y'] = y_pos
return text
async def get_onchain_stat(stat_slug: str, gerty):
text = []
if isinstance(gerty.mempool_endpoint, str):
async with httpx.AsyncClient() as client:
difficulty = []
if (
stat_slug == "onchain_difficulty_epoch_progress" or
stat_slug == "onchain_difficulty_retarget_date" or
stat_slug == "onchain_difficulty_blocks_remaining" or
stat_slug == "onchain_difficulty_epoch_time_remaining"
):
r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
if r:
difficulty.append(r.json())
onchain.append({"difficulty":difficulty})
mempool = []
r = await client.get(gerty.mempool_endpoint + "/api/v1/fees/mempool-blocks")
if r:
mempool.append(r.json())
onchain.append({"mempool":mempool})
threed = []
r = await client.get(gerty.mempool_endpoint + "/api/v1/mining/hashrate/3d")
if r:
threed.append(r.json())
onchain.append({"threed":threed})
if stat_slug == "onchain_difficulty_epoch_progress":
stat = round(r.json()['progressPercent'])
text.append(get_text_item_dict("Progress through current difficulty epoch", 15))
text.append(get_text_item_dict("{0}%".format(stat), 80))
elif stat_slug == "onchain_difficulty_retarget_date":
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", 15))
text.append(get_text_item_dict(dt, 40))
elif stat_slug == "onchain_difficulty_blocks_remaining":
stat = r.json()['remainingBlocks']
text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 15))
text.append(get_text_item_dict("{0}".format(format_number(stat)), 80))
elif stat_slug == "onchain_difficulty_epoch_time_remaining":
stat = r.json()['remainingTime']
text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 15))
text.append(get_text_item_dict(get_time_remaining(stat / 1000, 4), 20))
return text
ln = []
if gerty.ln_stats and isinstance(gerty.mempool_endpoint, str):
async def get_mempool_stat(stat_slug: str, gerty):
text = []
if isinstance(gerty.mempool_endpoint, str):
async with httpx.AsyncClient() as client:
r = await client.get(gerty.mempool_endpoint + "/api/v1/lightning/statistics/latest")
if r:
ln.append(r.json())
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'])
text.append(get_text_item_dict("Transactions in the mempool", 15))
text.append(get_text_item_dict("{0}".format(format_number(stat)), 80))
return text
return {"name":gerty.name, "wallets":wallets, "sats_quote":satoshi, "exchange":exchange, "onchain":onchain, "ln":ln}
def get_date_suffix(dayNumber):
if 4 <= dayNumber <= 20 or 24 <= dayNumber <= 30:
return "th"
else:
return ["st", "nd", "rd"][dayNumber % 10 - 1]
# format a number for nice display output
def format_number(number):
return ("{:,}".format(round(number)))
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),
)
result = []
for name, count in intervals:
value = seconds // count
if value:
seconds -= value * count
if value == 1:
name = name.rstrip('s')
result.append("{} {}".format(round(value), name))
return ', '.join(result[:granularity])