black/prettier

This commit is contained in:
Ben Arc
2021-07-07 10:19:16 +01:00
parent dd5080ac5a
commit 85bf0ebb15
10 changed files with 166 additions and 172 deletions

View File

@@ -5,7 +5,6 @@ The TwitchAlerts extension allows you to integrate Bitcoin Lightning (and on-cha
Try to include an image
<img src="https://i.imgur.com/9i4xcQB.png">
<h2>If your extension has API endpoints, include useful ones here</h2>
<code>curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/YOUR-EXTENSION/api/v1/EXAMPLE -d '{"amount":"100","memo":"TwitchAlerts"}' -H "X-Api-Key: YOUR_WALLET-ADMIN/INVOICE-KEY"</code>

View File

@@ -3,10 +3,9 @@ from lnbits.db import Database
db = Database("ext_twitchalerts")
twitchalerts_ext: Blueprint = Blueprint("twitchalerts",
__name__,
static_folder="static",
template_folder="templates")
twitchalerts_ext: Blueprint = Blueprint(
"twitchalerts", __name__, static_folder="static", template_folder="templates"
)
from .views_api import * # noqa
from .views import * # noqa

View File

@@ -79,11 +79,12 @@ async def post_donation(donation_id: str) -> tuple:
"""
donation = await get_donation(donation_id)
if not donation:
return (jsonify({"message":
"Donation not found!"}), HTTPStatus.BAD_REQUEST)
return (jsonify({"message": "Donation not found!"}), HTTPStatus.BAD_REQUEST)
if donation.posted:
return (jsonify({"message": "Donation has already been posted!"}),
HTTPStatus.BAD_REQUEST)
return (
jsonify({"message": "Donation has already been posted!"}),
HTTPStatus.BAD_REQUEST,
)
service = await get_service(donation.service)
if service.servicename == "Streamlabs":
url = "https://streamlabs.com/api/v1.0/donations"
@@ -100,13 +101,13 @@ async def post_donation(donation_id: str) -> tuple:
print(response.json())
status = [s for s in list(HTTPStatus) if s == response.status_code][0]
elif service.servicename == "StreamElements":
return (jsonify({"message": "StreamElements not yet supported!"}),
HTTPStatus.BAD_REQUEST)
return (
jsonify({"message": "StreamElements not yet supported!"}),
HTTPStatus.BAD_REQUEST,
)
else:
return (jsonify({"message":
"Unsopported servicename"}), HTTPStatus.BAD_REQUEST)
await db.execute("UPDATE Donations SET posted = 1 WHERE id = ?",
(donation_id, ))
return (jsonify({"message": "Unsopported servicename"}), HTTPStatus.BAD_REQUEST)
await db.execute("UPDATE Donations SET posted = 1 WHERE id = ?", (donation_id,))
return (jsonify(response.json()), status)
@@ -150,8 +151,7 @@ async def create_service(
return service
async def get_service(service_id: int,
by_state: str = None) -> Optional[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
@@ -159,18 +159,15 @@ async def get_service(service_id: int,
streamer via typos like 2 -> 3.
"""
if by_state:
row = await db.fetchone("SELECT * FROM Services WHERE state = ?",
(by_state, ))
row = await db.fetchone("SELECT * FROM Services WHERE state = ?", (by_state,))
else:
row = await db.fetchone("SELECT * FROM Services WHERE id = ?",
(service_id, ))
row = await db.fetchone("SELECT * FROM Services WHERE id = ?", (service_id,))
return Service.from_row(row) if row else None
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, ))
rows = await db.fetchall("SELECT * FROM Services WHERE wallet = ?", (wallet_id,))
return [Service.from_row(row) for row in rows] if rows else None
@@ -192,7 +189,7 @@ async def authenticate_service(service_id, code, redirect_uri):
async with httpx.AsyncClient() as client:
response = (await client.post(url, data=data)).json()
print(response)
token = response['access_token']
token = response["access_token"]
success = await service_add_token(service_id, token)
return f"/twitchalerts/?usr={user}", success
@@ -218,40 +215,37 @@ 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, ))
rows = await db.fetchall("SELECT * FROM Donations WHERE service = ?",
(service_id, ))
await db.execute("DELETE FROM Services WHERE id = ?", (service_id,))
rows = await db.fetchall("SELECT * FROM Donations WHERE service = ?", (service_id,))
for row in rows:
await delete_donation(row["id"])
async def get_donation(donation_id: str) -> Optional[Donation]:
"""Return a Donation"""
row = await db.fetchone("SELECT * FROM Donations WHERE id = ?",
(donation_id, ))
row = await db.fetchone("SELECT * FROM Donations WHERE id = ?", (donation_id,))
return Donation.from_row(row) if row else None
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, ))
rows = await db.fetchall("SELECT * FROM Donations WHERE wallet = ?", (wallet_id,))
return [Donation.from_row(row) for row in rows] if rows else None
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, ))
await db.execute("DELETE FROM Donations WHERE id = ?", (donation_id,))
await delete_charge(donation_id)
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))
row = await db.fetchone("SELECT * FROM Donations WHERE id = ?",
(donation_id, ))
await db.execute(
f"UPDATE Donations SET {q} WHERE id = ?", (*kwargs.values(), donation_id)
)
row = await db.fetchone("SELECT * FROM Donations WHERE id = ?", (donation_id,))
assert row, "Newly updated donation couldn't be retrieved"
return Donation(**row)
@@ -259,9 +253,9 @@ 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))
row = await db.fetchone("SELECT * FROM Services WHERE id = ?",
(service_id, ))
await db.execute(
f"UPDATE Services SET {q} WHERE id = ?", (*kwargs.values(), service_id)
)
row = await db.fetchone("SELECT * FROM Services WHERE id = ?", (service_id,))
assert row, "Newly updated service couldn't be retrieved"
return Service(**row)

View File

@@ -1,6 +1,7 @@
async def m001_initial(db):
await db.execute("""
await db.execute(
"""
CREATE TABLE IF NOT EXISTS Services (
id INTEGER PRIMARY KEY AUTOINCREMENT,
state TEXT NOT NULL,
@@ -13,9 +14,11 @@ async def m001_initial(db):
authenticated BOOLEAN NOT NULL,
token TEXT
);
""")
"""
)
await db.execute("""
await db.execute(
"""
CREATE TABLE IF NOT EXISTS Donations (
id TEXT PRIMARY KEY,
wallet TEXT NOT NULL,
@@ -28,4 +31,5 @@ async def m001_initial(db):
posted BOOLEAN NOT NULL,
FOREIGN KEY(service) REFERENCES Services(id)
);
""")
"""
)

View File

@@ -6,6 +6,7 @@ class Donation(NamedTuple):
"""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 # Name of the donor
@@ -26,6 +27,7 @@ class Service(NamedTuple):
Currently, Streamlabs is the only supported Service.
"""
id: int
state: str # A random hash used during authentication
twitchuser: str # The Twitch streamer's username

View File

@@ -7,8 +7,9 @@
Accept Bitcoin donations on Twitch, and integrate them into your alerts.
Present your viewers with a simple donation page, and add those donations
to Streamlabs to play alerts on your stream!<br />
For detailed setup instructions, check out <a href="https://github.com/Fittiboy/bitcoin-on-twitch">
this guide!</a><br />
For detailed setup instructions, check out
<a href="https://github.com/Fittiboy/bitcoin-on-twitch"> this guide!</a
><br />
<small>
Created by, <a href="https://github.com/Fittiboy">Fitti</a></small
>

View File

@@ -18,8 +18,8 @@
dense
v-model.number="donationDialog.data.sats"
type="number"
min=1
suffix=sats
min="1"
suffix="sats"
:rules="[val => val > 0 || 'Choose a positive number of sats!']"
label="Amount of sats"
></q-input>

View File

@@ -151,9 +151,7 @@
<div class="col-12 col-md-4 col-lg-5 q-gutter-y-md">
<q-card>
<q-card-section>
<h6 class="text-subtitle1 q-my-none">
LNbits Twitch Alerts extension
</h6>
<h6 class="text-subtitle1 q-my-none">LNbits Twitch Alerts extension</h6>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
@@ -231,21 +229,21 @@
</div>
{% endblock %} {% block scripts %} {{ window_vars(user) }}
<script>
var mapTwitchAlerts = function(obj) {
var mapTwitchAlerts = function (obj) {
obj.date = Quasar.utils.date.formatDate(
new Date(obj.time * 1000),
'YYYY-MM-DD HH:mm'
)
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount)
obj.authUrl = ['/twitchalerts/api/v1/getaccess/', obj.id].join('')
obj.displayUrl = ['/twitchalerts/', obj.state].join('')
obj.displayUrl = ['/twitchalerts/', obj.state].join('')
return obj
}
new Vue({
el: '#vue',
mixins: [windowMixin],
data: function() {
data: function () {
return {
servicenames: ['Streamlabs'],
services: [],
@@ -253,7 +251,12 @@
servicesTable: {
columns: [
{name: 'id', align: 'left', label: 'ID', field: 'id'},
{name: 'twitchuser', align: 'left', label: 'Twitch Username', field: 'twitchuser'},
{
name: 'twitchuser',
align: 'left',
label: 'Twitch Username',
field: 'twitchuser'
},
{name: 'wallet', align: 'left', label: 'Wallet', field: 'wallet'},
{
name: 'servicename',
@@ -286,7 +289,12 @@
},
donationsTable: {
columns: [
{name: 'service', align: 'left', label: 'Service', field: 'service'},
{
name: 'service',
align: 'left',
label: 'Service',
field: 'service'
},
{name: 'donor', align: 'left', label: 'Donor', field: 'donor'},
{name: 'ltext', align: 'left', label: 'Message', field: 'ltext'},
{name: 'sats', align: 'left', label: 'Sats', field: 'sats'}
@@ -302,7 +310,7 @@
}
},
methods: {
getDonations: function() {
getDonations: function () {
var self = this
LNbits.api
@@ -311,40 +319,40 @@
'/twitchalerts/api/v1/donations',
this.g.user.wallets[0].inkey
)
.then(function(response) {
self.donations = response.data.map(function(obj) {
.then(function (response) {
self.donations = response.data.map(function (obj) {
return mapTwitchAlerts(obj)
})
})
},
deleteDonation: function(donationId) {
deleteDonation: function (donationId) {
var self = this
var donations = _.findWhere(this.donations, {id: donationId})
LNbits.utils
.confirmDialog('Are you sure you want to delete this donation?')
.onOk(function() {
.onOk(function () {
LNbits.api
.request(
'DELETE',
'/twitchalerts/api/v1/donations/' + donationId,
_.findWhere(self.g.user.wallets, {id: donations.wallet}).inkey
)
.then(function(response) {
self.donations = _.reject(self.donations, function(obj) {
.then(function (response) {
self.donations = _.reject(self.donations, function (obj) {
return obj.id == ticketId
})
})
.catch(function(error) {
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
})
},
exportdonationsCSV: function() {
exportdonationsCSV: function () {
LNbits.utils.exportCSV(this.donationsTable.columns, this.donations)
},
getServices: function() {
getServices: function () {
var self = this
LNbits.api
@@ -353,13 +361,13 @@
'/twitchalerts/api/v1/services',
this.g.user.wallets[0].inkey
)
.then(function(response) {
self.services = response.data.map(function(obj) {
.then(function (response) {
self.services = response.data.map(function (obj) {
return mapTwitchAlerts(obj)
})
})
},
sendServiceData: function() {
sendServiceData: function () {
var wallet = _.findWhere(this.g.user.wallets, {
id: this.serviceDialog.data.wallet
})
@@ -368,20 +376,20 @@
this.createService(wallet, data)
},
createService: function(wallet, data) {
createService: function (wallet, data) {
var self = this
LNbits.api
.request('POST', '/twitchalerts/api/v1/services', wallet.inkey, data)
.then(function(response) {
.then(function (response) {
self.services.push(mapTwitchAlerts(response.data))
self.serviceDialog.show = false
self.serviceDialog.data = {}
})
.catch(function(error) {
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
updateserviceDialog: function(serviceId) {
updateserviceDialog: function (serviceId) {
var link = _.findWhere(this.services, {id: serviceId})
console.log(link.id)
this.serviceDialog.data.id = link.id
@@ -392,35 +400,35 @@
this.serviceDialog.data.client_secret = link.client_secret
this.serviceDialog.show = true
},
deleteService: function(servicesId) {
deleteService: function (servicesId) {
var self = this
var services = _.findWhere(this.services, {id: servicesId})
LNbits.utils
.confirmDialog('Are you sure you want to delete this service link?')
.onOk(function() {
.onOk(function () {
LNbits.api
.request(
'DELETE',
'/twitchalerts/api/v1/services/' + servicesId,
_.findWhere(self.g.user.wallets, {id: services.wallet}).inkey
)
.then(function(response) {
self.services = _.reject(self.services, function(obj) {
.then(function (response) {
self.services = _.reject(self.services, function (obj) {
return obj.id == servicesId
})
})
.catch(function(error) {
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
})
},
exportservicesCSV: function() {
exportservicesCSV: function () {
LNbits.utils.exportCSV(this.servicesTable.columns, this.services)
}
},
created: function() {
created: function () {
if (this.g.user.wallets.length) {
this.getDonations()
this.getServices()

View File

@@ -21,6 +21,6 @@ async def donation(state):
service = await get_service(0, by_state=state)
if not service:
abort(HTTPStatus.NOT_FOUND, "Service does not exist.")
return await render_template("twitchalerts/display.html",
twitchuser=service.twitchuser,
service=service.id)
return await render_template(
"twitchalerts/display.html", twitchuser=service.twitchuser, service=service.id
)

View File

@@ -6,11 +6,22 @@ from lnbits.core.crud import get_wallet, get_user
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, get_donations,
delete_donation, create_service, get_service, get_services,
authenticate_service, update_donation, update_service,
delete_service)
from .crud import (
get_charge_details,
get_service_redirect_uri,
create_donation,
post_donation,
get_donation,
get_donations,
delete_donation,
create_service,
get_service,
get_services,
authenticate_service,
update_donation,
update_service,
delete_service,
)
from ..satspay.crud import create_charge, get_charge
@@ -18,30 +29,14 @@ from ..satspay.crud import create_charge, get_charge
@api_check_wallet_key("invoice")
@api_validate_post_request(
schema={
"twitchuser": {
"type": "string",
"required": True
},
"client_id": {
"type": "string",
"required": True
},
"client_secret": {
"type": "string",
"required": True
},
"wallet": {
"type": "string",
"required": True
},
"servicename": {
"type": "string",
"required": True
},
"onchain": {
"type": "string"
}
})
"twitchuser": {"type": "string", "required": True},
"client_id": {"type": "string", "required": True},
"client_secret": {"type": "string", "required": True},
"wallet": {"type": "string", "required": True},
"servicename": {"type": "string", "required": True},
"onchain": {"type": "string"},
}
)
async def api_create_service():
"""Create a service, which holds data about how/where to post donations"""
service = await create_service(**g.data)
@@ -65,16 +60,14 @@ async def api_get_access(service_id):
"client_id": service.client_id,
"redirect_uri": redirect_uri,
"scope": "donations.create",
"state": service.state
"state": service.state,
}
endpoint_url = "https://streamlabs.com/api/v1.0/authorize/?"
querystring = "&".join(
[f"{key}={value}" for key, value in params.items()])
querystring = "&".join([f"{key}={value}" for key, value in params.items()])
redirect_url = endpoint_url + querystring
return redirect(redirect_url)
else:
return (jsonify({"message":
"Service does not exist!"}), HTTPStatus.BAD_REQUEST)
return (jsonify({"message": "Service does not exist!"}), HTTPStatus.BAD_REQUEST)
@twitchalerts_ext.route("/api/v1/authenticate/<service_id>", methods=["GET"])
@@ -84,40 +77,32 @@ async def api_authenticate_service(service_id):
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')
code = request.args.get("code")
state = request.args.get("state")
service = await get_service(service_id)
if service.state != state:
return (jsonify({"message":
"State doesn't match!"}), HTTPStatus.BAD_Request)
return (jsonify({"message": "State doesn't match!"}), HTTPStatus.BAD_Request)
redirect_uri = request.scheme + "://" + request.headers["Host"]
redirect_uri += f"/twitchalerts/api/v1/authenticate/{service_id}"
url, success = await authenticate_service(service_id, code, redirect_uri)
if success:
return redirect(url)
else:
return (jsonify({"message": "Service already authenticated!"}),
HTTPStatus.BAD_REQUEST)
return (
jsonify({"message": "Service already authenticated!"}),
HTTPStatus.BAD_REQUEST,
)
@twitchalerts_ext.route("/api/v1/donations", methods=["POST"])
@api_validate_post_request(
schema={
"name": {
"type": "string"
},
"sats": {
"type": "integer",
"required": True
},
"service": {
"type": "integer",
"required": True
},
"message": {
"type": "string"
}
})
"name": {"type": "string"},
"sats": {"type": "integer", "required": True},
"service": {"type": "integer", "required": True},
"message": {"type": "string"},
}
)
async def api_create_donation():
"""Take data from donation form and return satspay charge"""
# Currency is hardcoded while frotnend is limited
@@ -126,7 +111,7 @@ async def api_create_donation():
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
amount = sats * (10 ** (-8)) * price
webhook_base = request.scheme + "://" + request.headers["Host"]
service_id = g.data["service"]
service = await get_service(service_id)
@@ -139,7 +124,8 @@ async def api_create_donation():
completelinktext="Back to Stream!",
webhook=webhook_base + "/twitchalerts/api/v1/postdonation",
description=description,
**charge_details)
**charge_details,
)
await create_donation(
id=charge.id,
wallet=service.wallet,
@@ -154,12 +140,11 @@ async def api_create_donation():
@twitchalerts_ext.route("/api/v1/postdonation", methods=["POST"])
@api_validate_post_request(schema={
"id": {
"type": "string",
"required": True
},
})
@api_validate_post_request(
schema={
"id": {"type": "string", "required": True},
}
)
async def api_post_donation():
"""Post a paid donation to Stremalabs/StreamElements.
@@ -170,8 +155,7 @@ async def api_post_donation():
if charge and charge.paid:
return await post_donation(donation_id)
else:
return (jsonify({"message":
"Not a paid charge!"}), HTTPStatus.BAD_REQUEST)
return (jsonify({"message": "Not a paid charge!"}), HTTPStatus.BAD_REQUEST)
@twitchalerts_ext.route("/api/v1/services", methods=["GET"])
@@ -184,8 +168,7 @@ async def api_get_services():
new_services = await get_services(wallet_id)
services += new_services if new_services else []
return (
jsonify([service._asdict()
for service in services] if services else []),
jsonify([service._asdict() for service in services] if services else []),
HTTPStatus.OK,
)
@@ -202,8 +185,7 @@ async def api_get_donations():
new_donations = await get_donations(wallet_id)
donations += new_donations if new_donations else []
return (
jsonify([donation._asdict()
for donation in donations] if donations else []),
jsonify([donation._asdict() for donation in donations] if donations else []),
HTTPStatus.OK,
)
@@ -216,17 +198,20 @@ async def api_update_donation(donation_id=None):
donation = await get_donation(donation_id)
if not donation:
return (jsonify({"message": "Donation does not exist."}),
HTTPStatus.NOT_FOUND)
return (
jsonify({"message": "Donation does not exist."}),
HTTPStatus.NOT_FOUND,
)
if donation.wallet != g.wallet.id:
return (jsonify({"message":
"Not your donation."}), HTTPStatus.FORBIDDEN)
return (jsonify({"message": "Not your donation."}), HTTPStatus.FORBIDDEN)
donation = await update_donation(donation_id, **g.data)
else:
return (jsonify({"message":
"No donation ID specified"}), HTTPStatus.BAD_REQUEST)
return (
jsonify({"message": "No donation ID specified"}),
HTTPStatus.BAD_REQUEST,
)
return jsonify(donation._asdict()), HTTPStatus.CREATED
@@ -238,17 +223,17 @@ async def api_update_service(service_id=None):
service = await get_service(service_id)
if not service:
return (jsonify({"message":
"Service does not exist."}), HTTPStatus.NOT_FOUND)
return (
jsonify({"message": "Service does not exist."}),
HTTPStatus.NOT_FOUND,
)
if service.wallet != g.wallet.id:
return (jsonify({"message":
"Not your service."}), HTTPStatus.FORBIDDEN)
return (jsonify({"message": "Not your service."}), HTTPStatus.FORBIDDEN)
service = await update_service(service_id, **g.data)
else:
return (jsonify({"message":
"No service ID specified"}), HTTPStatus.BAD_REQUEST)
return (jsonify({"message": "No service ID specified"}), HTTPStatus.BAD_REQUEST)
return jsonify(service._asdict()), HTTPStatus.CREATED
@@ -258,11 +243,12 @@ 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 (jsonify({"message":
"No donation with this ID!"}), HTTPStatus.NOT_FOUND)
return (jsonify({"message": "No donation with this ID!"}), HTTPStatus.NOT_FOUND)
if donation.wallet != g.wallet.id:
return (jsonify({"message": "Not authorized to delete this donation!"
}), HTTPStatus.FORBIDDEN)
return (
jsonify({"message": "Not authorized to delete this donation!"}),
HTTPStatus.FORBIDDEN,
)
await delete_donation(donation_id)
return "", HTTPStatus.NO_CONTENT
@@ -274,11 +260,12 @@ 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 (jsonify({"message":
"No service with this ID!"}), HTTPStatus.NOT_FOUND)
return (jsonify({"message": "No service with this ID!"}), HTTPStatus.NOT_FOUND)
if service.wallet != g.wallet.id:
return (jsonify({"message": "Not authorized to delete this service!"}),
HTTPStatus.FORBIDDEN)
return (
jsonify({"message": "Not authorized to delete this service!"}),
HTTPStatus.FORBIDDEN,
)
await delete_service(service_id)
return "", HTTPStatus.NO_CONTENT