Merge remote-tracking branch 'origin/main' into main
This commit is contained in:
commit
72f2ec0be8
12 changed files with 89 additions and 38 deletions
|
|
@ -84,18 +84,19 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
|
||||||
def check_funding_source(app: FastAPI) -> None:
|
def check_funding_source(app: FastAPI) -> None:
|
||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
async def check_wallet_status():
|
async def check_wallet_status():
|
||||||
error_message, balance = await WALLET.status()
|
while True:
|
||||||
if error_message:
|
error_message, balance = await WALLET.status()
|
||||||
|
if not error_message:
|
||||||
|
break
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
f" × The backend for {WALLET.__class__.__name__} isn't working properly: '{error_message}'",
|
f" × The backend for {WALLET.__class__.__name__} isn't working properly: '{error_message}'",
|
||||||
RuntimeWarning,
|
RuntimeWarning,
|
||||||
)
|
)
|
||||||
|
print("Retrying connection to backend in 5 seconds...")
|
||||||
sys.exit(4)
|
await asyncio.sleep(5)
|
||||||
else:
|
print(
|
||||||
print(
|
f" ✔️ {WALLET.__class__.__name__} seems to be connected and with a balance of {balance} msat."
|
||||||
f" ✔️ {WALLET.__class__.__name__} seems to be connected and with a balance of {balance} msat."
|
)
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def register_routes(app: FastAPI) -> None:
|
def register_routes(app: FastAPI) -> None:
|
||||||
|
|
|
||||||
|
|
@ -24,24 +24,24 @@ from lnbits.decorators import (
|
||||||
WalletTypeInfo,
|
WalletTypeInfo,
|
||||||
get_key_type,
|
get_key_type,
|
||||||
)
|
)
|
||||||
from lnbits.helpers import url_for
|
from lnbits.helpers import url_for, urlsafe_short_hash
|
||||||
from lnbits.requestvars import g
|
from lnbits.requestvars import g
|
||||||
|
from lnbits.settings import LNBITS_ADMIN_USERS, LNBITS_SITE_TITLE
|
||||||
from lnbits.utils.exchange_rates import (
|
from lnbits.utils.exchange_rates import (
|
||||||
currencies,
|
currencies,
|
||||||
fiat_amount_as_satoshis,
|
fiat_amount_as_satoshis,
|
||||||
satoshis_amount_as_fiat,
|
satoshis_amount_as_fiat,
|
||||||
)
|
)
|
||||||
from lnbits.settings import LNBITS_SITE_TITLE
|
|
||||||
|
|
||||||
from .. import core_app, db
|
from .. import core_app, db
|
||||||
from ..crud import (
|
from ..crud import (
|
||||||
|
create_payment,
|
||||||
get_payments,
|
get_payments,
|
||||||
get_standalone_payment,
|
get_standalone_payment,
|
||||||
save_balance_check,
|
|
||||||
update_wallet,
|
|
||||||
create_payment,
|
|
||||||
get_wallet,
|
get_wallet,
|
||||||
|
save_balance_check,
|
||||||
update_payment_status,
|
update_payment_status,
|
||||||
|
update_wallet,
|
||||||
)
|
)
|
||||||
from ..services import (
|
from ..services import (
|
||||||
InvoiceFailure,
|
InvoiceFailure,
|
||||||
|
|
@ -52,8 +52,6 @@ from ..services import (
|
||||||
perform_lnurlauth,
|
perform_lnurlauth,
|
||||||
)
|
)
|
||||||
from ..tasks import api_invoice_listeners
|
from ..tasks import api_invoice_listeners
|
||||||
from lnbits.settings import LNBITS_ADMIN_USERS
|
|
||||||
from lnbits.helpers import urlsafe_short_hash
|
|
||||||
|
|
||||||
|
|
||||||
@core_app.get("/api/v1/wallet")
|
@core_app.get("/api/v1/wallet")
|
||||||
|
|
@ -503,12 +501,13 @@ async def api_lnurlscan(code: str):
|
||||||
|
|
||||||
@core_app.post("/api/v1/payments/decode")
|
@core_app.post("/api/v1/payments/decode")
|
||||||
async def api_payments_decode(data: str = Query(None)):
|
async def api_payments_decode(data: str = Query(None)):
|
||||||
|
print(data)
|
||||||
try:
|
try:
|
||||||
if data["data"][:5] == "LNURL":
|
if data[:5] == "LNURL":
|
||||||
url = lnurl.decode(data["data"])
|
url = lnurl.decode(data)
|
||||||
return {"domain": url}
|
return {"domain": url}
|
||||||
else:
|
else:
|
||||||
invoice = bolt11.decode(data["data"])
|
invoice = bolt11.decode(data)
|
||||||
return {
|
return {
|
||||||
"payment_hash": invoice.payment_hash,
|
"payment_hash": invoice.payment_hash,
|
||||||
"amount_msat": invoice.amount_msat,
|
"amount_msat": invoice.amount_msat,
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ async def get_copilot(copilot_id: str) -> Copilots:
|
||||||
|
|
||||||
async def get_copilots(user: str) -> List[Copilots]:
|
async def get_copilots(user: str) -> List[Copilots]:
|
||||||
rows = await db.fetchall(
|
rows = await db.fetchall(
|
||||||
"SELECT * FROM copilot.newer_copilots WHERE user = ?", (user,)
|
'SELECT * FROM copilot.newer_copilots WHERE "user" = ?', (user,)
|
||||||
)
|
)
|
||||||
return [Copilots(**row) for row in rows]
|
return [Copilots(**row) for row in rows]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ async def delete_ticket(payment_hash: str) -> None:
|
||||||
|
|
||||||
|
|
||||||
async def delete_event_tickets(event_id: str) -> None:
|
async def delete_event_tickets(event_id: str) -> None:
|
||||||
await db.execute("DELETE FROM events.tickets WHERE event = ?", (event_id,))
|
await db.execute("DELETE FROM events.ticket WHERE event = ?", (event_id,))
|
||||||
|
|
||||||
|
|
||||||
# EVENTS
|
# EVENTS
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ async def create_jukebox(
|
||||||
juke_id = urlsafe_short_hash()
|
juke_id = urlsafe_short_hash()
|
||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO jukebox.jukebox (id, user, title, wallet, sp_user, sp_secret, sp_access_token, sp_refresh_token, sp_device, sp_playlists, price, profit)
|
INSERT INTO jukebox.jukebox (id, "user", title, wallet, sp_user, sp_secret, sp_access_token, sp_refresh_token, sp_device, sp_playlists, price, profit)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
|
|
@ -41,6 +41,7 @@ async def update_jukebox(
|
||||||
q = ", ".join([f"{field[0]} = ?" for field in data])
|
q = ", ".join([f"{field[0]} = ?" for field in data])
|
||||||
items = [f"{field[1]}" for field in data]
|
items = [f"{field[1]}" for field in data]
|
||||||
items.append(juke_id)
|
items.append(juke_id)
|
||||||
|
q = q.replace("user", '"user"', 1) # hack to make user be "user"!
|
||||||
await db.execute(f"UPDATE jukebox.jukebox SET {q} WHERE id = ?", (items))
|
await db.execute(f"UPDATE jukebox.jukebox SET {q} WHERE id = ?", (items))
|
||||||
row = await db.fetchone("SELECT * FROM jukebox.jukebox WHERE id = ?", (juke_id,))
|
row = await db.fetchone("SELECT * FROM jukebox.jukebox WHERE id = ?", (juke_id,))
|
||||||
return Jukebox(**row) if row else None
|
return Jukebox(**row) if row else None
|
||||||
|
|
@ -57,11 +58,11 @@ async def get_jukebox_by_user(user: str) -> Optional[Jukebox]:
|
||||||
|
|
||||||
|
|
||||||
async def get_jukeboxs(user: str) -> List[Jukebox]:
|
async def get_jukeboxs(user: str) -> List[Jukebox]:
|
||||||
rows = await db.fetchall("SELECT * FROM jukebox.jukebox WHERE user = ?", (user,))
|
rows = await db.fetchall('SELECT * FROM jukebox.jukebox WHERE "user" = ?', (user,))
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if row.sp_playlists == None:
|
if row.sp_playlists == None:
|
||||||
await delete_jukebox(row.id)
|
await delete_jukebox(row.id)
|
||||||
rows = await db.fetchall("SELECT * FROM jukebox.jukebox WHERE user = ?", (user,))
|
rows = await db.fetchall('SELECT * FROM jukebox.jukebox WHERE "user" = ?', (user,))
|
||||||
|
|
||||||
return [Jukebox(**row) for row in rows]
|
return [Jukebox(**row) for row in rows]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,6 @@ async def api_check_credentials_check(
|
||||||
juke_id: str = Query(None), wallet: WalletTypeInfo = Depends(require_admin_key)
|
juke_id: str = Query(None), wallet: WalletTypeInfo = Depends(require_admin_key)
|
||||||
):
|
):
|
||||||
jukebox = await get_jukebox(juke_id)
|
jukebox = await get_jukebox(juke_id)
|
||||||
|
|
||||||
return jukebox
|
return jukebox
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -442,7 +441,7 @@ async def api_get_jukebox_currently(
|
||||||
token = await api_get_token(juke_id)
|
token = await api_get_token(juke_id)
|
||||||
if token == False:
|
if token == False:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.FORBIDDEN, detail="INvoice not paid"
|
status_code=HTTPStatus.FORBIDDEN, detail="Invoice not paid"
|
||||||
)
|
)
|
||||||
elif retry:
|
elif retry:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
@ -456,5 +455,5 @@ async def api_get_jukebox_currently(
|
||||||
)
|
)
|
||||||
except:
|
except:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Something went wrong"
|
status_code=HTTPStatus.NOT_FOUND, detail="Something went wrong, or no song is playing yet"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -117,9 +117,10 @@
|
||||||
{% raw %}
|
{% raw %}
|
||||||
<template v-slot:header="props">
|
<template v-slot:header="props">
|
||||||
<q-tr :props="props">
|
<q-tr :props="props">
|
||||||
|
<q-th auto-width></q-th>
|
||||||
<q-th auto-width></q-th>
|
<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">
|
||||||
{{ col.label }}
|
{{ col.label }}
|
||||||
</q-th>
|
</q-th>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -136,9 +137,19 @@
|
||||||
:href="'mailto:' + props.row.email"
|
:href="'mailto:' + props.row.email"
|
||||||
></q-btn>
|
></q-btn>
|
||||||
</q-td>
|
</q-td>
|
||||||
|
<q-td auto-width>
|
||||||
|
<q-btn
|
||||||
|
unelevated
|
||||||
|
dense
|
||||||
|
size="xs"
|
||||||
|
icon="launch"
|
||||||
|
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
||||||
|
@click="ticketCard(props)"
|
||||||
|
><q-tooltip> Click to show ticket </q-tooltip></q-btn>
|
||||||
|
</q-td>
|
||||||
|
|
||||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
{{ col.value }}
|
{{ col.label == "Ticket" ? col.value.length > 20 ? `${col.value.substring(0, 20)}...` : col.value : col.value }}
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
||||||
<q-td auto-width>
|
<q-td auto-width>
|
||||||
|
|
@ -249,6 +260,29 @@
|
||||||
</q-form>
|
</q-form>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
<!-- Read Ticket Dialog -->
|
||||||
|
<q-dialog v-model="ticketDialog.show" position="top">
|
||||||
|
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
||||||
|
{% raw %}
|
||||||
|
<q-card-section>
|
||||||
|
<h4 class="text-subtitle1 q-my-none">
|
||||||
|
<i>{{this.ticketDialog.data.name}}</i> sent a ticket
|
||||||
|
</h4>
|
||||||
|
<div v-if="this.ticketDialog.data.email">
|
||||||
|
<small>{{this.ticketDialog.data.email}}</small>
|
||||||
|
</div>
|
||||||
|
<small>{{this.ticketDialog.data.date}}</small>
|
||||||
|
</q-card-section>
|
||||||
|
<q-separator></q-separator>
|
||||||
|
<q-card-section>
|
||||||
|
<p>{{this.ticketDialog.data.content}}</p>
|
||||||
|
</q-card-section>
|
||||||
|
{% endraw %}
|
||||||
|
<q-card-actions align="right">
|
||||||
|
<q-btn flat label="CLOSE" color="primary" v-close-popup />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -318,6 +352,10 @@
|
||||||
formDialog: {
|
formDialog: {
|
||||||
show: false,
|
show: false,
|
||||||
data: {flatrate: false}
|
data: {flatrate: false}
|
||||||
|
},
|
||||||
|
ticketDialog: {
|
||||||
|
show: false,
|
||||||
|
data: {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -372,6 +410,16 @@
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
ticketCard(ticket){
|
||||||
|
this.ticketDialog.show = true
|
||||||
|
let {date, email, ltext, name} = ticket.row
|
||||||
|
this.ticketDialog.data = {
|
||||||
|
date,
|
||||||
|
email,
|
||||||
|
content: ltext,
|
||||||
|
name
|
||||||
|
}
|
||||||
|
},
|
||||||
exportticketsCSV: function () {
|
exportticketsCSV: function () {
|
||||||
LNbits.utils.exportCSV(this.ticketsTable.columns, this.tickets)
|
LNbits.utils.exportCSV(this.ticketsTable.columns, this.tickets)
|
||||||
},
|
},
|
||||||
|
|
@ -421,12 +469,13 @@
|
||||||
},
|
},
|
||||||
updateformDialog: function (formId) {
|
updateformDialog: function (formId) {
|
||||||
var link = _.findWhere(this.forms, {id: formId})
|
var link = _.findWhere(this.forms, {id: formId})
|
||||||
|
console.log("LINK", link)
|
||||||
|
|
||||||
this.formDialog.data.id = link.id
|
this.formDialog.data.id = link.id
|
||||||
this.formDialog.data.wallet = link.wallet
|
this.formDialog.data.wallet = link.wallet
|
||||||
this.formDialog.data.name = link.name
|
this.formDialog.data.name = link.name
|
||||||
this.formDialog.data.description = link.description
|
this.formDialog.data.description = link.description
|
||||||
this.formDialog.data.flatrate = link.flatrate
|
this.formDialog.data.flatrate = Boolean(link.flatrate)
|
||||||
this.formDialog.data.amount = link.amount
|
this.formDialog.data.amount = link.amount
|
||||||
this.formDialog.show = true
|
this.formDialog.show = true
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ class Service(BaseModel):
|
||||||
onchain: Optional[str]
|
onchain: Optional[str]
|
||||||
servicename: str # Currently, this will just always be "Streamlabs"
|
servicename: str # Currently, this will just always be "Streamlabs"
|
||||||
authenticated: bool # Whether a token (see below) has been acquired yet
|
authenticated: bool # Whether a token (see below) has been acquired yet
|
||||||
token: Optional[int] # The token with which to authenticate requests
|
token: Optional[str] # The token with which to authenticate requests
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_row(cls, row: Row) -> "Service":
|
def from_row(cls, row: Row) -> "Service":
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@
|
||||||
donationDialog: {
|
donationDialog: {
|
||||||
show: false,
|
show: false,
|
||||||
data: {
|
data: {
|
||||||
name: '',
|
name: null,
|
||||||
sats: '',
|
sats: '',
|
||||||
message: ''
|
message: ''
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ from starlette.responses import RedirectResponse
|
||||||
|
|
||||||
from lnbits.core.crud import get_user
|
from lnbits.core.crud import get_user
|
||||||
from lnbits.decorators import WalletTypeInfo, get_key_type
|
from lnbits.decorators import WalletTypeInfo, get_key_type
|
||||||
|
from lnbits.extensions.satspay.models import CreateCharge
|
||||||
from lnbits.extensions.streamalerts.models import (
|
from lnbits.extensions.streamalerts.models import (
|
||||||
CreateDonation,
|
CreateDonation,
|
||||||
CreateService,
|
CreateService,
|
||||||
|
|
@ -113,17 +114,18 @@ async def api_create_donation(data: CreateDonation, request: Request):
|
||||||
service_id = data.service
|
service_id = data.service
|
||||||
service = await get_service(service_id)
|
service = await get_service(service_id)
|
||||||
charge_details = await get_charge_details(service.id)
|
charge_details = await get_charge_details(service.id)
|
||||||
name = data.name
|
name = data.name if data.name else "Anonymous"
|
||||||
|
|
||||||
description = f"{sats} sats donation from {name} to {service.twitchuser}"
|
description = f"{sats} sats donation from {name} to {service.twitchuser}"
|
||||||
charge = await create_charge(
|
create_charge_data = CreateCharge(
|
||||||
amount=sats,
|
amount=sats,
|
||||||
completelink=f"https://twitch.tv/{service.twitchuser}",
|
completelink=f"https://twitch.tv/{service.twitchuser}",
|
||||||
completelinktext="Back to Stream!",
|
completelinktext="Back to Stream!",
|
||||||
webhook=webhook_base + "/streamalerts/api/v1/postdonation",
|
webhook=webhook_base + "/streamalerts/api/v1/postdonation",
|
||||||
description=description,
|
description=description,
|
||||||
**charge_details,
|
**charge_details
|
||||||
)
|
)
|
||||||
|
charge = await create_charge(user=charge_details["user"], data=create_charge_data)
|
||||||
await create_donation(
|
await create_donation(
|
||||||
id=charge.id,
|
id=charge.id,
|
||||||
wallet=service.wallet,
|
wallet=service.wallet,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from sqlite3 import Row
|
from sqlite3 import Row
|
||||||
from typing import NamedTuple, Optional
|
from typing import Optional
|
||||||
|
|
||||||
from fastapi.param_functions import Query
|
from fastapi.param_functions import Query
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
@ -26,7 +26,7 @@ class createTip(BaseModel):
|
||||||
message: str = ""
|
message: str = ""
|
||||||
|
|
||||||
|
|
||||||
class Tip(NamedTuple):
|
class Tip(BaseModel):
|
||||||
"""A Tip represents a single donation"""
|
"""A Tip represents a single donation"""
|
||||||
|
|
||||||
id: str # This ID always corresponds to a satspay charge ID
|
id: str # This ID always corresponds to a satspay charge ID
|
||||||
|
|
@ -55,7 +55,7 @@ class createTips(BaseModel):
|
||||||
message: str
|
message: str
|
||||||
|
|
||||||
|
|
||||||
class TipJar(NamedTuple):
|
class TipJar(BaseModel):
|
||||||
"""A TipJar represents a user's tip jar"""
|
"""A TipJar represents a user's tip jar"""
|
||||||
|
|
||||||
id: int
|
id: int
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ async def api_get_addresses(wallet_id, w: WalletTypeInfo = Depends(get_key_type)
|
||||||
async def api_update_mempool(
|
async def api_update_mempool(
|
||||||
endpoint: str = Query(...), w: WalletTypeInfo = Depends(require_admin_key)
|
endpoint: str = Query(...), w: WalletTypeInfo = Depends(require_admin_key)
|
||||||
):
|
):
|
||||||
mempool = await update_mempool(endpoint, user=w.wallet.user)
|
mempool = await update_mempool(**{"endpoint": endpoint}, user=w.wallet.user)
|
||||||
return mempool.dict()
|
return mempool.dict()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue