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 Try to include an image
<img src="https://i.imgur.com/9i4xcQB.png"> <img src="https://i.imgur.com/9i4xcQB.png">
<h2>If your extension has API endpoints, include useful ones here</h2> <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> <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") db = Database("ext_twitchalerts")
twitchalerts_ext: Blueprint = Blueprint("twitchalerts", twitchalerts_ext: Blueprint = Blueprint(
__name__, "twitchalerts", __name__, static_folder="static", template_folder="templates"
static_folder="static", )
template_folder="templates")
from .views_api import * # noqa from .views_api import * # noqa
from .views 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) donation = await get_donation(donation_id)
if not donation: if not donation:
return (jsonify({"message": return (jsonify({"message": "Donation not found!"}), HTTPStatus.BAD_REQUEST)
"Donation not found!"}), HTTPStatus.BAD_REQUEST)
if donation.posted: if donation.posted:
return (jsonify({"message": "Donation has already been posted!"}), return (
HTTPStatus.BAD_REQUEST) jsonify({"message": "Donation has already been posted!"}),
HTTPStatus.BAD_REQUEST,
)
service = await get_service(donation.service) service = await get_service(donation.service)
if service.servicename == "Streamlabs": if service.servicename == "Streamlabs":
url = "https://streamlabs.com/api/v1.0/donations" url = "https://streamlabs.com/api/v1.0/donations"
@@ -100,13 +101,13 @@ async def post_donation(donation_id: str) -> tuple:
print(response.json()) print(response.json())
status = [s for s in list(HTTPStatus) if s == response.status_code][0] status = [s for s in list(HTTPStatus) if s == response.status_code][0]
elif service.servicename == "StreamElements": elif service.servicename == "StreamElements":
return (jsonify({"message": "StreamElements not yet supported!"}), return (
HTTPStatus.BAD_REQUEST) jsonify({"message": "StreamElements not yet supported!"}),
HTTPStatus.BAD_REQUEST,
)
else: else:
return (jsonify({"message": return (jsonify({"message": "Unsopported servicename"}), HTTPStatus.BAD_REQUEST)
"Unsopported servicename"}), HTTPStatus.BAD_REQUEST) await db.execute("UPDATE Donations SET posted = 1 WHERE id = ?", (donation_id,))
await db.execute("UPDATE Donations SET posted = 1 WHERE id = ?",
(donation_id, ))
return (jsonify(response.json()), status) return (jsonify(response.json()), status)
@@ -150,8 +151,7 @@ async def create_service(
return service return service
async def get_service(service_id: int, async def get_service(service_id: int, by_state: str = None) -> Optional[Service]:
by_state: str = None) -> Optional[Service]:
"""Return a service either by ID or, available, by state """Return a service either by ID or, available, by state
Each Service's donation page is reached through its "state" hash 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. streamer via typos like 2 -> 3.
""" """
if by_state: if by_state:
row = await db.fetchone("SELECT * FROM Services WHERE state = ?", row = await db.fetchone("SELECT * FROM Services WHERE state = ?", (by_state,))
(by_state, ))
else: else:
row = await db.fetchone("SELECT * FROM Services WHERE id = ?", row = await db.fetchone("SELECT * FROM Services WHERE id = ?", (service_id,))
(service_id, ))
return Service.from_row(row) if row else None return Service.from_row(row) if row else None
async def get_services(wallet_id: str) -> Optional[list]: async def get_services(wallet_id: str) -> Optional[list]:
"""Return all services belonging assigned to the wallet_id""" """Return all services belonging assigned to the wallet_id"""
rows = await db.fetchall("SELECT * FROM Services WHERE wallet = ?", rows = await db.fetchall("SELECT * FROM Services WHERE wallet = ?", (wallet_id,))
(wallet_id, ))
return [Service.from_row(row) for row in rows] if rows else None 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: async with httpx.AsyncClient() as client:
response = (await client.post(url, data=data)).json() response = (await client.post(url, data=data)).json()
print(response) print(response)
token = response['access_token'] token = response["access_token"]
success = await service_add_token(service_id, token) success = await service_add_token(service_id, token)
return f"/twitchalerts/?usr={user}", success 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: async def delete_service(service_id: int) -> None:
"""Delete a Service and all corresponding Donations""" """Delete a Service and all corresponding Donations"""
await db.execute("DELETE FROM Services WHERE id = ?", (service_id, )) await db.execute("DELETE FROM Services WHERE id = ?", (service_id,))
rows = await db.fetchall("SELECT * FROM Donations WHERE service = ?", rows = await db.fetchall("SELECT * FROM Donations WHERE service = ?", (service_id,))
(service_id, ))
for row in rows: for row in rows:
await delete_donation(row["id"]) await delete_donation(row["id"])
async def get_donation(donation_id: str) -> Optional[Donation]: async def get_donation(donation_id: str) -> Optional[Donation]:
"""Return a Donation""" """Return a Donation"""
row = await db.fetchone("SELECT * FROM Donations WHERE id = ?", row = await db.fetchone("SELECT * FROM Donations WHERE id = ?", (donation_id,))
(donation_id, ))
return Donation.from_row(row) if row else None return Donation.from_row(row) if row else None
async def get_donations(wallet_id: str) -> Optional[list]: async def get_donations(wallet_id: str) -> Optional[list]:
"""Return all Donations assigned to wallet_id""" """Return all Donations assigned to wallet_id"""
rows = await db.fetchall("SELECT * FROM Donations WHERE wallet = ?", rows = await db.fetchall("SELECT * FROM Donations WHERE wallet = ?", (wallet_id,))
(wallet_id, ))
return [Donation.from_row(row) for row in rows] if rows else None return [Donation.from_row(row) for row in rows] if rows else None
async def delete_donation(donation_id: str) -> None: async def delete_donation(donation_id: str) -> None:
"""Delete a Donation and its corresponding statspay charge""" """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) await delete_charge(donation_id)
async def update_donation(donation_id: str, **kwargs) -> Donation: async def update_donation(donation_id: str, **kwargs) -> Donation:
"""Update a Donation""" """Update a Donation"""
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
await db.execute(f"UPDATE Donations SET {q} WHERE id = ?", await db.execute(
(*kwargs.values(), donation_id)) f"UPDATE Donations SET {q} WHERE id = ?", (*kwargs.values(), donation_id)
row = await db.fetchone("SELECT * FROM Donations WHERE id = ?", )
(donation_id, )) row = await db.fetchone("SELECT * FROM Donations WHERE id = ?", (donation_id,))
assert row, "Newly updated donation couldn't be retrieved" assert row, "Newly updated donation couldn't be retrieved"
return Donation(**row) 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: async def update_service(service_id: str, **kwargs) -> Donation:
"""Update a service""" """Update a service"""
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
await db.execute(f"UPDATE Services SET {q} WHERE id = ?", await db.execute(
(*kwargs.values(), service_id)) f"UPDATE Services SET {q} WHERE id = ?", (*kwargs.values(), service_id)
row = await db.fetchone("SELECT * FROM Services WHERE id = ?", )
(service_id, )) row = await db.fetchone("SELECT * FROM Services WHERE id = ?", (service_id,))
assert row, "Newly updated service couldn't be retrieved" assert row, "Newly updated service couldn't be retrieved"
return Service(**row) return Service(**row)

View File

@@ -1,6 +1,7 @@
async def m001_initial(db): async def m001_initial(db):
await db.execute(""" await db.execute(
"""
CREATE TABLE IF NOT EXISTS Services ( CREATE TABLE IF NOT EXISTS Services (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
state TEXT NOT NULL, state TEXT NOT NULL,
@@ -13,9 +14,11 @@ async def m001_initial(db):
authenticated BOOLEAN NOT NULL, authenticated BOOLEAN NOT NULL,
token TEXT token TEXT
); );
""") """
)
await db.execute(""" await db.execute(
"""
CREATE TABLE IF NOT EXISTS Donations ( CREATE TABLE IF NOT EXISTS Donations (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
wallet TEXT NOT NULL, wallet TEXT NOT NULL,
@@ -28,4 +31,5 @@ async def m001_initial(db):
posted BOOLEAN NOT NULL, posted BOOLEAN NOT NULL,
FOREIGN KEY(service) REFERENCES Services(id) 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 """A Donation simply contains all the necessary information about a
user's donation to a streamer user's donation to a streamer
""" """
id: str # This ID always corresponds to a satspay charge ID id: str # This ID always corresponds to a satspay charge ID
wallet: str wallet: str
name: str # Name of the donor name: str # Name of the donor
@@ -26,6 +27,7 @@ class Service(NamedTuple):
Currently, Streamlabs is the only supported Service. Currently, Streamlabs is the only supported Service.
""" """
id: int id: int
state: str # A random hash used during authentication state: str # A random hash used during authentication
twitchuser: str # The Twitch streamer's username twitchuser: str # The Twitch streamer's username

View File

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

View File

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

View File

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

View File

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