mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2024-11-20 10:39:59 +01:00
Cleanup, Comments, Docstrings
This commit is contained in:
parent
f88c622f94
commit
435787ad93
@ -1,6 +1,6 @@
|
||||
<h1>Example Extension</h1>
|
||||
<h2>*tagline*</h2>
|
||||
This is an TwitchAlerts extension to help you organise and build you own.
|
||||
The TwitchAlerts extension allows you to integrate Bitcoin Lightning (and on-chain) paymnents in to your existing Streamlabs alerts!
|
||||
|
||||
Try to include an image
|
||||
<img src="https://i.imgur.com/9i4xcQB.png">
|
||||
|
@ -4,7 +4,10 @@ from lnbits.db import Database
|
||||
db = Database("ext_twitchalerts")
|
||||
|
||||
twitchalerts_ext: Blueprint = Blueprint(
|
||||
"twitchalerts", __name__, static_folder="static", template_folder="templates"
|
||||
"twitchalerts",
|
||||
__name__,
|
||||
static_folder="static",
|
||||
template_folder="templates"
|
||||
)
|
||||
|
||||
|
||||
|
@ -14,7 +14,19 @@ from lnbits.helpers import urlsafe_short_hash
|
||||
from lnbits.core.crud import get_wallet
|
||||
|
||||
|
||||
async def get_service_redirect_uri(request, service_id):
|
||||
"""Return the service's redirect URI, to be given to the third party API"""
|
||||
uri_base = request.scheme + "://"
|
||||
uri_base += request.headers["Host"] + "/twitchalerts/api/v1"
|
||||
redirect_uri = uri_base + f"/authenticate/{service_id}"
|
||||
return redirect_uri
|
||||
|
||||
|
||||
async def get_charge_details(service_id):
|
||||
"""Return the default details for a satspay charge
|
||||
|
||||
These might be different depending for services implemented in the future.
|
||||
"""
|
||||
details = {
|
||||
"time": 1440,
|
||||
}
|
||||
@ -39,6 +51,7 @@ async def create_donation(
|
||||
message: str = "",
|
||||
posted: bool = False,
|
||||
) -> Donation:
|
||||
"""Create a new Donation"""
|
||||
await db.execute(
|
||||
"""
|
||||
INSERT INTO Donations (
|
||||
@ -70,6 +83,10 @@ async def create_donation(
|
||||
|
||||
|
||||
async def post_donation(donation_id: str) -> tuple:
|
||||
"""Post donations to their respective third party APIs
|
||||
|
||||
If the donation has already been posted, it will not be posted again.
|
||||
"""
|
||||
donation = await get_donation(donation_id)
|
||||
if not donation:
|
||||
return (
|
||||
@ -125,6 +142,7 @@ async def create_service(
|
||||
state: str = None,
|
||||
onchain: str = None,
|
||||
) -> Service:
|
||||
"""Create a new Service"""
|
||||
result = await db.execute(
|
||||
"""
|
||||
INSERT INTO Services (
|
||||
@ -157,6 +175,12 @@ async def create_service(
|
||||
|
||||
async def get_service(service_id: int,
|
||||
by_state: str = None) -> Optional[Service]:
|
||||
"""Return a service either by ID or, available, by state
|
||||
|
||||
Each Service's donation page is reached through its "state" hash
|
||||
instead of the ID, preventing accidental payments to the wrong
|
||||
streamer via typos like 2 -> 3.
|
||||
"""
|
||||
if by_state:
|
||||
row = await db.fetchone(
|
||||
"SELECT * FROM Services WHERE state = ?",
|
||||
@ -171,6 +195,7 @@ async def get_service(service_id: int,
|
||||
|
||||
|
||||
async def get_services(wallet_id: str) -> Optional[list]:
|
||||
"""Return all services belonging assigned to the wallet_id"""
|
||||
rows = await db.fetchall(
|
||||
"SELECT * FROM Services WHERE wallet = ?",
|
||||
(wallet_id,)
|
||||
@ -179,6 +204,7 @@ async def get_services(wallet_id: str) -> Optional[list]:
|
||||
|
||||
|
||||
async def authenticate_service(service_id, code, redirect_uri):
|
||||
"""Use authentication code from third party API to retreive access token"""
|
||||
# The API token is passed in the querystring as 'code'
|
||||
service = await get_service(service_id)
|
||||
wallet = await get_wallet(service.wallet)
|
||||
@ -201,6 +227,12 @@ async def authenticate_service(service_id, code, redirect_uri):
|
||||
|
||||
|
||||
async def service_add_token(service_id, token):
|
||||
"""Add access token to its corresponding Service
|
||||
|
||||
This also sets authenticated = 1 to make sure the token
|
||||
is not overwritten.
|
||||
Tokens for Streamlabs never need to be refreshed.
|
||||
"""
|
||||
if (await get_service(service_id)).authenticated:
|
||||
return False
|
||||
await db.execute(
|
||||
@ -211,6 +243,7 @@ async def service_add_token(service_id, token):
|
||||
|
||||
|
||||
async def delete_service(service_id: int) -> None:
|
||||
"""Delete a Service and all corresponding Donations"""
|
||||
await db.execute(
|
||||
"DELETE FROM Services WHERE id = ?",
|
||||
(service_id,)
|
||||
@ -224,6 +257,7 @@ async def delete_service(service_id: int) -> None:
|
||||
|
||||
|
||||
async def get_donation(donation_id: str) -> Optional[Donation]:
|
||||
"""Return a Donation"""
|
||||
row = await db.fetchone(
|
||||
"SELECT * FROM Donations WHERE id = ?",
|
||||
(donation_id,)
|
||||
@ -232,6 +266,7 @@ async def get_donation(donation_id: str) -> Optional[Donation]:
|
||||
|
||||
|
||||
async def get_donations(wallet_id: str) -> Optional[list]:
|
||||
"""Return all Donations assigned to wallet_id"""
|
||||
rows = await db.fetchall(
|
||||
"SELECT * FROM Donations WHERE wallet = ?",
|
||||
(wallet_id,)
|
||||
@ -240,6 +275,7 @@ async def get_donations(wallet_id: str) -> Optional[list]:
|
||||
|
||||
|
||||
async def delete_donation(donation_id: str) -> None:
|
||||
"""Delete a Donation and its corresponding statspay charge"""
|
||||
await db.execute(
|
||||
"DELETE FROM Donations WHERE id = ?",
|
||||
(donation_id,)
|
||||
@ -248,6 +284,7 @@ async def delete_donation(donation_id: str) -> None:
|
||||
|
||||
|
||||
async def update_donation(donation_id: str, **kwargs) -> Donation:
|
||||
"""Update a Donation"""
|
||||
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
|
||||
await db.execute(f"UPDATE Donations SET {q} WHERE id = ?",
|
||||
(*kwargs.values(), donation_id))
|
||||
@ -258,6 +295,7 @@ async def update_donation(donation_id: str, **kwargs) -> Donation:
|
||||
|
||||
|
||||
async def update_service(service_id: str, **kwargs) -> Donation:
|
||||
"""Update a service"""
|
||||
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
|
||||
await db.execute(f"UPDATE Services SET {q} WHERE id = ?",
|
||||
(*kwargs.values(), service_id))
|
||||
|
@ -3,15 +3,18 @@ from typing import NamedTuple, Optional
|
||||
|
||||
|
||||
class Donation(NamedTuple):
|
||||
id: str
|
||||
"""A Donation simply contains all the necessary information about a
|
||||
user's donation to a streamer
|
||||
"""
|
||||
id: str # This ID always corresponds to a satspay charge ID
|
||||
wallet: str
|
||||
name: str
|
||||
message: str
|
||||
cur_code: str
|
||||
name: str # Name of the donor
|
||||
message: str # Donation message
|
||||
cur_code: str # Three letter currency code accepted by Streamlabs
|
||||
sats: int
|
||||
amount: float
|
||||
service: int
|
||||
posted: bool
|
||||
amount: float # The donation amount after fiat conversion
|
||||
service: int # The ID of the corresponding Service
|
||||
posted: bool # Whether the donation has already been posted to a Service
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row) -> "Donation":
|
||||
@ -19,16 +22,20 @@ class Donation(NamedTuple):
|
||||
|
||||
|
||||
class Service(NamedTuple):
|
||||
"""A Service represents an integration with a third-party API
|
||||
|
||||
Currently, Streamlabs is the only supported Service.
|
||||
"""
|
||||
id: int
|
||||
state: str
|
||||
twitchuser: str
|
||||
client_id: str
|
||||
client_secret: str
|
||||
state: str # A random hash used during authentication
|
||||
twitchuser: str # The Twitch streamer's username
|
||||
client_id: str # Third party service Client ID
|
||||
client_secret: str # Secret corresponding to the Client ID
|
||||
wallet: str
|
||||
onchain: str
|
||||
servicename: str
|
||||
authenticated: bool
|
||||
token: Optional[int]
|
||||
servicename: str # Currently, this will just always be "Streamlabs"
|
||||
authenticated: bool # Whether a token (see below) has been acquired yet
|
||||
token: Optional[int] # The token with which to authenticate requests
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row) -> "Service":
|
||||
|
@ -11,11 +11,13 @@ from .crud import get_service
|
||||
@validate_uuids(["usr"], required=True)
|
||||
@check_user_exists()
|
||||
async def index():
|
||||
"""Return the extension's settings page"""
|
||||
return await render_template("twitchalerts/index.html", user=g.user)
|
||||
|
||||
|
||||
@twitchalerts_ext.route("/<state>")
|
||||
async def donation(state):
|
||||
"""Return the donation form for the Service corresponding to state"""
|
||||
service = await get_service(0, by_state=state)
|
||||
if not service:
|
||||
abort(HTTPStatus.NOT_FOUND, "Service does not exist.")
|
||||
|
@ -8,6 +8,7 @@ from lnbits.utils.exchange_rates import btc_price
|
||||
from . import twitchalerts_ext
|
||||
from .crud import (
|
||||
get_charge_details,
|
||||
get_service_redirect_uri,
|
||||
create_donation,
|
||||
post_donation,
|
||||
get_donation,
|
||||
@ -48,11 +49,12 @@ async def api_create_service():
|
||||
|
||||
@twitchalerts_ext.route("/api/v1/getaccess/<service_id>", methods=["GET"])
|
||||
async def api_get_access(service_id):
|
||||
"""Redirect to Streamlabs' Approve/Decline page for API access for Service
|
||||
with service_id
|
||||
"""
|
||||
service = await get_service(service_id)
|
||||
if service:
|
||||
uri_base = request.scheme + "://"
|
||||
uri_base += request.headers["Host"] + "/twitchalerts/api/v1"
|
||||
redirect_uri = uri_base + f"/authenticate/{service_id}"
|
||||
redirect_uri = await get_service_redirect_uri(request, service_id)
|
||||
params = {
|
||||
"response_type": "code",
|
||||
"client_id": service.client_id,
|
||||
@ -75,6 +77,11 @@ async def api_get_access(service_id):
|
||||
|
||||
@twitchalerts_ext.route("/api/v1/authenticate/<service_id>", methods=["GET"])
|
||||
async def api_authenticate_service(service_id):
|
||||
"""Endpoint visited via redirect during third party API authentication
|
||||
|
||||
If successful, an API access token will be added to the service, and
|
||||
the user will be redirected to index.html.
|
||||
"""
|
||||
code = request.args.get('code')
|
||||
state = request.args.get('state')
|
||||
service = await get_service(service_id)
|
||||
@ -105,11 +112,13 @@ async def api_authenticate_service(service_id):
|
||||
}
|
||||
)
|
||||
async def api_create_donation():
|
||||
"""Takes data from donation form and creates+returns SatsPay charge"""
|
||||
"""Take data from donation form and return satspay charge"""
|
||||
# Currency is hardcoded while frotnend is limited
|
||||
cur_code = "USD"
|
||||
price = await btc_price(cur_code)
|
||||
sats = g.data["sats"]
|
||||
message = g.data.get("message", "")
|
||||
# Fiat amount is calculated here while frontend is limited
|
||||
price = await btc_price(cur_code)
|
||||
amount = sats * (10 ** (-8)) * price
|
||||
webhook_base = request.scheme + "://" + request.headers["Host"]
|
||||
service_id = g.data["service"]
|
||||
@ -147,7 +156,7 @@ async def api_create_donation():
|
||||
}
|
||||
)
|
||||
async def api_post_donation():
|
||||
"""Posts a paid donation to Stremalabs/StreamElements.
|
||||
"""Post a paid donation to Stremalabs/StreamElements.
|
||||
|
||||
This endpoint acts as a webhook for the SatsPayServer extension."""
|
||||
data = await request.get_json(force=True)
|
||||
@ -165,6 +174,7 @@ async def api_post_donation():
|
||||
@twitchalerts_ext.route("/api/v1/services", methods=["GET"])
|
||||
@api_check_wallet_key("invoice")
|
||||
async def api_get_services():
|
||||
"""Return list of all services assigned to wallet with given invoice key"""
|
||||
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
|
||||
services = []
|
||||
for wallet_id in wallet_ids:
|
||||
@ -181,6 +191,9 @@ async def api_get_services():
|
||||
@twitchalerts_ext.route("/api/v1/donations", methods=["GET"])
|
||||
@api_check_wallet_key("invoice")
|
||||
async def api_get_donations():
|
||||
"""Return list of all donations assigned to wallet with given invoice
|
||||
key
|
||||
"""
|
||||
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
|
||||
donations = []
|
||||
for wallet_id in wallet_ids:
|
||||
@ -197,6 +210,7 @@ async def api_get_donations():
|
||||
@twitchalerts_ext.route("/api/v1/donations/<donation_id>", methods=["PUT"])
|
||||
@api_check_wallet_key("invoice")
|
||||
async def api_update_donation(donation_id=None):
|
||||
"""Update a donation with the data given in the request"""
|
||||
if donation_id:
|
||||
donation = await get_donation(donation_id)
|
||||
|
||||
@ -224,6 +238,7 @@ async def api_update_donation(donation_id=None):
|
||||
@twitchalerts_ext.route("/api/v1/services/<service_id>", methods=["PUT"])
|
||||
@api_check_wallet_key("invoice")
|
||||
async def api_update_service(service_id=None):
|
||||
"""Update a service with the data given in the request"""
|
||||
if service_id:
|
||||
service = await get_service(service_id)
|
||||
|
||||
@ -251,6 +266,7 @@ async def api_update_service(service_id=None):
|
||||
@twitchalerts_ext.route("/api/v1/donations/<donation_id>", methods=["DELETE"])
|
||||
@api_check_wallet_key("invoice")
|
||||
async def api_delete_donation(donation_id):
|
||||
"""Delete the donation with the given donation_id"""
|
||||
donation = await get_donation(donation_id)
|
||||
if not donation:
|
||||
return (
|
||||
@ -270,6 +286,7 @@ async def api_delete_donation(donation_id):
|
||||
@twitchalerts_ext.route("/api/v1/services/<service_id>", methods=["DELETE"])
|
||||
@api_check_wallet_key("invoice")
|
||||
async def api_delete_service(service_id):
|
||||
"""Delete the service with the given service_id"""
|
||||
service = await get_service(service_id)
|
||||
if not service:
|
||||
return (
|
||||
|
Loading…
Reference in New Issue
Block a user