This commit is contained in:
Ben Arc 2021-06-07 23:29:40 +01:00
parent ed60b7f6ee
commit cb6c3629ea
7 changed files with 188 additions and 215 deletions

View File

@ -3,6 +3,7 @@ from typing import List, Optional
from . import db from . import db
from .models import Jukebox, JukeboxPayment from .models import Jukebox, JukeboxPayment
from lnbits.helpers import urlsafe_short_hash from lnbits.helpers import urlsafe_short_hash
import json
async def create_jukebox( async def create_jukebox(
@ -21,8 +22,8 @@ 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 (id, user, title, wallet, sp_user, sp_secret, sp_access_token, sp_refresh_token, sp_device, sp_playlists, price, profit, queue) INSERT INTO jukebox (id, user, title, wallet, sp_user, sp_secret, sp_access_token, sp_refresh_token, sp_device, sp_playlists, price, profit)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", """,
( (
juke_id, juke_id,
@ -37,7 +38,6 @@ async def create_jukebox(
sp_playlists, sp_playlists,
int(price), int(price),
0, 0,
"[]",
), ),
) )
jukebox = await get_jukebox(juke_id) jukebox = await get_jukebox(juke_id)

View File

@ -17,9 +17,7 @@ async def m001_initial(db):
sp_device TEXT, sp_device TEXT,
sp_playlists TEXT, sp_playlists TEXT,
price INTEGER, price INTEGER,
profit INTEGER, profit INTEGER
queue TEXT,
last_checked TIMESTAMP NOT NULL DEFAULT (strftime('%s', 'now'))
); );
""" """
) )

View File

@ -21,8 +21,6 @@ class Jukebox(NamedTuple):
sp_playlists: str sp_playlists: str
price: int price: int
profit: int profit: int
queue: list
last_checked: int
@classmethod @classmethod
def from_row(cls, row: Row) -> "Jukebox": def from_row(cls, row: Row) -> "Jukebox":

View File

@ -199,7 +199,7 @@ new Vue({
console.log(self.devices) console.log(self.devices)
console.log("this.devices") console.log("this.devices")
setTimeout(function () { setTimeout(function () {
if (self.devices.length < 1 && self.playlists.length < 1) { if (self.devices.length < 1 || self.playlists.length < 1) {
self.$q.notify({ self.$q.notify({
spinner: true, spinner: true,
color: 'red', color: 'red',

View File

@ -9,8 +9,7 @@
<img style="width: 100px" :src="currentPlay.image" /> <img style="width: 100px" :src="currentPlay.image" />
</div> </div>
<div class="col-8"> <div class="col-8">
<strong style="font-size: 20px">{{ currentPlay.name }}</strong <strong style="font-size: 20px">{{ currentPlay.name }}</strong><br />
><br />
<strong style="font-size: 15px">{{ currentPlay.artist }}</strong> <strong style="font-size: 15px">{{ currentPlay.artist }}</strong>
</div> </div>
</div> </div>
@ -20,29 +19,15 @@
<q-card class="q-mt-lg"> <q-card class="q-mt-lg">
<q-card-section> <q-card-section>
<p style="font-size: 22px">Pick a song</p> <p style="font-size: 22px">Pick a song</p>
<q-select <q-select outlined v-model="playlist" :options="playlists" label="playlists" @input="selectPlaylist()">
outlined </q-select>
v-model="playlist"
:options="playlists"
label="playlists"
@input="selectPlaylist()"
></q-select>
</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>
<q-virtual-scroll <q-virtual-scroll style="max-height: 300px" :items="currentPlaylist" separator>
style="max-height: 300px"
:items="currentPlaylist"
separator
>
<template v-slot="{ item, index }"> <template v-slot="{ item, index }">
<q-item <q-item :key="index" dense clickable v-ripple
:key="index" @click="payForSong(item.id, item.name, item.artist, item.image)">
dense
clickable
v-ripple
@click="payForSong(item.id, item.name, item.artist, item.image)"
>
<q-item-section> <q-item-section>
<q-item-label> <q-item-label>
{{ item.name }} - ({{ item.artist }}) {{ item.name }} - ({{ item.artist }})
@ -70,8 +55,7 @@
</q-card-section> </q-card-section>
<br /> <br />
<div class="row q-mt-lg q-gutter-sm"> <div class="row q-mt-lg q-gutter-sm">
<q-btn outline color="grey" @click="getInvoice(receive.id)" <q-btn outline color="grey" @click="getInvoice(receive.id)">Play for {% endraw %}{{ price }}{% raw %} sats
>Play for {% endraw %}{{ price }}{% raw %} sats
</q-btn> </q-btn>
</div> </div>
</q-card> </q-card>
@ -79,16 +63,10 @@
<q-dialog v-model="receive.dialogues.second" position="top"> <q-dialog v-model="receive.dialogues.second" position="top">
<q-card class="q-pa-lg lnbits__dialog-card"> <q-card class="q-pa-lg lnbits__dialog-card">
<q-responsive :ratio="1" class="q-mx-xl q-mb-md"> <q-responsive :ratio="1" class="q-mx-xl q-mb-md">
<qrcode <qrcode :value="'lightning:' + receive.paymentReq" :options="{width: 800}" class="rounded-borders"></qrcode>
:value="'lightning:' + receive.paymentReq"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
</q-responsive> </q-responsive>
<div class="row q-mt-lg q-gutter-sm"> <div class="row q-mt-lg q-gutter-sm">
<q-btn outline color="grey" @click="copyText(receive.paymentReq)" <q-btn outline color="grey" @click="copyText(receive.paymentReq)">Copy invoice</q-btn>
>Copy invoice</q-btn
>
</div> </div>
</q-card> </q-card>
</q-dialog> </q-dialog>
@ -106,7 +84,7 @@
return { return {
currentPlaylist: [], currentPlaylist: [],
currentlyPlaying: {}, currentlyPlaying: {},
cancelListener: () => {}, cancelListener: () => { },
playlists: {}, playlists: {},
playlist: '', playlist: '',
heavyList: [], heavyList: [],
@ -133,13 +111,6 @@
} }
}, },
methods: { methods: {
startPaymentNotifier() {
this.cancelListener()
this.cancelListener = LNbits.events.onInvoicePaid(
this.selectedWallet,
payment => console.log(payment)
)
},
cancelPayment: function () { cancelPayment: function () {
this.paymentReq = null this.paymentReq = null
clearInterval(this.paymentDialog.checker) clearInterval(this.paymentDialog.checker)
@ -147,7 +118,7 @@
this.paymentDialog.dismissMsg() this.paymentDialog.dismissMsg()
} }
}, },
closeReceiveDialog() {}, closeReceiveDialog() { },
payForSong(song_id, name, artist, image) { payForSong(song_id, name, artist, image) {
self = this self = this
self.receive.name = name self.receive.name = name
@ -162,78 +133,86 @@
.request( .request(
'GET', 'GET',
'/jukebox/api/v1/jukebox/jb/invoice/' + '/jukebox/api/v1/jukebox/jb/invoice/' +
'{{ juke_id }}' + '{{ juke_id }}' +
'/' + '/' +
song_id song_id
) )
.then(function (response) { .then(function (response) {
self.receive.paymentReq = response.data[0][1] self.receive.paymentReq = response.data[0][1]
self.receive.paymentHash = response.data[0][0] self.receive.paymentHash = response.data[0][0]
self.receive.dialogues.second = true self.receive.dialogues.second = true
var refreshIntervalId = setInterval(function () {
self.checkInvoice(self.receive.paymentHash) var paymentChecker = setInterval(function () {
if(self.paid){ if (!self.paid) {
self.checkInvoice(self.receive.paymentHash, '{{ juke_id }}')
}
if (self.paid) {
clearInterval(paymentChecker)
self.paid = true
self.receive.dialogues.first = false self.receive.dialogues.first = false
self.receive.dialogues.second = false self.receive.dialogues.second = false
self.$q.notify({ self.$q.notify({
message: message:
'Processing', 'Processing',
}) })
console.log('/api/v1/jukebox/jb/invoicepaid/' + self.receive.paymentHash + '/{{ juke_id }}')
LNbits.api LNbits.api
.request( .request(
'GET', 'GET',
'/jukebox/api/v1/jukebox/jb/invoicep/{{ juke_id }}/' + self.receive.paymentHash) '/jukebox/api/v1/jukebox/jb/invoicep/' + song_id + '/{{ juke_id }}/' + self.receive.paymentHash)
.then(function (response) { .then(function (response1) {
self.$q.notify({ console.log(response1)
color: 'green', if (response1.data[2] == song_id) {
message:
'Success! "' + self.receive.name + '" will be played soon',
timeout: 2000
})
clearInterval(refreshIntervalId)
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
} self.$q.notify({
}, 2000) color: 'green',
message:
'Success! "' + self.receive.name + '" will be played soon',
timeout: 2000
})
self.paid = false
response1 = []
}
})
.catch(err => {
LNbits.utils.notifyApiError(err)
self.paid = false
response1 = []
})
}
}, 4000)
}) })
.catch(err => { .catch(err => {
LNbits.utils.notifyApiError(err) LNbits.utils.notifyApiError(err)
}) })
}, },
checkInvoice: function (paymentHash) { checkInvoice(juke_id, paymentHash) {
var self = this var self = this
LNbits.api LNbits.api
.request( .request(
'GET', 'GET',
'/public/v1/payment/' + paymentHash, '/jukebox/api/v1/jukebox/jb/checkinvoice/' + juke_id + '/' + paymentHash,
'filla' 'filla'
) )
.then(function (response) { .then(function (response) {
console.log(response) console.log(response.data.paid)
if (response) { self.paid = response.data.paid
self.paid = true
return
}
else{
self.paid = false
return
}
}) })
.catch(function (error) { .catch(function (error) {
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
}, },
getCurrent() { getCurrent() {
axios
.get('/jukebox/api/v1/jukebox/jb/currently/{{juke_id}}') LNbits.api
.request(
'GET',
'/jukebox/api/v1/jukebox/jb/currently/{{juke_id}}')
.then(function (res) { .then(function (res) {
if (res.data.id) { if (res.data.id) {
console.log(res.data)
self.currentlyPlaying = res.data self.currentlyPlaying = res.data
} }
}) })
@ -247,20 +226,18 @@
.request( .request(
'GET', 'GET',
'/jukebox/api/v1/jukebox/jb/playlist/' + '/jukebox/api/v1/jukebox/jb/playlist/' +
'{{ juke_id }}' + '{{ juke_id }}' +
'/' + '/' +
self.playlist.split(',')[0].split('-')[1] self.playlist.split(',')[0].split('-')[1]
) )
.then(function (response) { .then(function (response) {
console.log(response.data)
self.currentPlaylist = response.data self.currentPlaylist = response.data
console.log(self.currentPlaylist[2].id)
}) })
.catch(err => { .catch(err => {
LNbits.utils.notifyApiError(err) LNbits.utils.notifyApiError(err)
}) })
}, },
currentSong() {} currentSong() { }
}, },
created() { created() {
this.getCurrent() this.getCurrent()
@ -271,27 +248,18 @@
.request( .request(
'GET', 'GET',
'/jukebox/api/v1/jukebox/jb/playlist/' + '/jukebox/api/v1/jukebox/jb/playlist/' +
'{{ juke_id }}' + '{{ juke_id }}' +
'/' + '/' +
self.playlists[0].split(',')[0].split('-')[1] self.playlists[0].split(',')[0].split('-')[1]
) )
.then(function (response) { .then(function (response) {
console.log(response.data)
self.currentPlaylist = response.data self.currentPlaylist = response.data
console.log(self.currentPlaylist[2].id)
}) })
.catch(err => { .catch(err => {
LNbits.utils.notifyApiError(err) LNbits.utils.notifyApiError(err)
}) })
// this.startPaymentNotifier() // this.startPaymentNotifier()
},
mounted() {
self = this
self.selectedWallet['inkey'] = '{{ inkey }}'
LNbits.events.onInvoicePaid(self.selectedWallet, payment =>
console.log(payment)
)
} }
}) })
</script> </script>

View File

@ -33,37 +33,3 @@ async def print_qr_codes(juke_id):
price=jukebox.price, price=jukebox.price,
inkey=jukebox.inkey, inkey=jukebox.inkey,
) )
##################WEBSOCKET ROUTES########################
connected_websockets = set()
def collect_websocket(func):
@wraps(func)
async def wrapper(*args, **kwargs):
global connected_websockets
send_channel, receive_channel = trio.open_memory_channel(0)
connected_websockets.add(send_channel)
try:
return await func(receive_channel, *args, **kwargs)
finally:
connected_websockets.remove(send_channel)
return wrapper
@jukebox_ext.websocket("/ws")
@collect_websocket
async def wss(receive_channel):
while True:
data = await receive_channel.receive()
await websocket.send(data)
async def broadcast(message):
print(connected_websockets)
for queue in connected_websockets:
await queue.send(f"{message}")

View File

@ -5,12 +5,12 @@ from base64 import urlsafe_b64encode
import base64 import base64
import json import json
import time import time
from lnbits.core.crud import get_wallet
from lnbits.core.services import create_invoice, check_invoice_status
from lnbits.decorators import api_check_wallet_key, api_validate_post_request from lnbits.decorators import api_check_wallet_key, api_validate_post_request
import httpx import httpx
from . import jukebox_ext from . import jukebox_ext
from .views import broadcast
from .crud import ( from .crud import (
create_jukebox, create_jukebox,
update_jukebox, update_jukebox,
@ -133,10 +133,9 @@ async def api_get_jukebox_song(juke_id, sp_playlist):
headers={"Authorization": "Bearer " + jukebox.sp_access_token}, headers={"Authorization": "Bearer " + jukebox.sp_access_token},
) )
if "items" not in r.json(): if "items" not in r.json():
if r.json()["error"]["status"] == 401: if r.status_code == 401:
token = await api_get_token(juke_id) token = await api_get_token(juke_id)
if token == False: if token == False:
return False return False
else: else:
return await api_get_jukebox_song(juke_id, sp_playlist) return await api_get_jukebox_song(juke_id, sp_playlist)
@ -208,60 +207,107 @@ async def api_get_jukebox_invoice(juke_id, song_id):
return jsonify(invoice, jukebox_payment) return jsonify(invoice, jukebox_payment)
@jukebox_ext.route("/api/v1/jukebox/jb/invoicep/<juke_id>/<pay_hash>", methods=["GET"]) @jukebox_ext.route(
async def api_get_jukebox_invoice_paid(juke_id, pay_hash): "/api/v1/jukebox/jb/checkinvoice/<pay_hash>/<juke_id>", methods=["GET"]
)
async def api_get_jukebox_invoice_check(pay_hash, juke_id):
jukebox = await get_jukebox(juke_id) jukebox = await get_jukebox(juke_id)
print(jukebox) try:
paid = await check_invoice_status(jukebox.wallet, pay_hash) status = await check_invoice_status(jukebox.wallet, pay_hash)
if paid.paid: is_paid = not status.pending
jukebox_payment = await update_jukebox_payment(pay_hash, paid=True) except Exception as exc:
else: return jsonify({"paid": False}), HTTPStatus.OK
return jsonify({"error": "Invoice not paid"}) if is_paid:
queue = await add_to_song_queue(jukebox_payment.song_id, jukebox_payment.juke_id) wallet = await get_wallet(jukebox.wallet)
return queue payment = await wallet.get_payment(pay_hash)
await payment.set_pending(False)
await update_jukebox_payment(pay_hash, paid=True)
return jsonify({"paid": True}), HTTPStatus.OK
return jsonify({"paid": False}), HTTPStatus.OK
@jukebox_ext.route("/api/v1/jukebox/jb/invoicep/<cunt>", methods=["GET"]) @jukebox_ext.route(
async def api_get_jukebox_invoice_cunt(cunt): "/api/v1/jukebox/jb/invoicep/<song_id>/<juke_id>/<pay_hash>", methods=["GET"]
)
return cunt async def api_get_jukebox_invoice_paid(song_id, juke_id, pay_hash):
############################QUEUE SONG
async def add_to_song_queue(song_id, juke_id):
jukebox = await get_jukebox(juke_id) jukebox = await get_jukebox(juke_id)
queue = jukebox.queue jukebox_payment = await get_jukebox_payment(pay_hash)
print(queue[0]) if jukebox_payment.paid:
queue.append(song_id) async with httpx.AsyncClient() as client:
# Add song to back of queue r = await client.get(
jukebox = await update_jukebox(juke_id=juke_id, queue=queue) "https://api.spotify.com/v1/me/player/currently-playing?market=ES",
# while loop for all tracks. Check 25 secs has passsed since last check. timeout=40,
queued = jukebox.queue headers={"Authorization": "Bearer " + jukebox.sp_access_token},
while len(queued) > 0: )
if (time.time() - jukebox.last_checked) > 25000: if r.status_code == 204:
song = await api_get_jukebox_currently(juke_id) async with httpx.AsyncClient() as client:
if song.track.id != queued[0]: uri = ["spotify:track:" + song_id]
r = await client.put(
"https://api.spotify.com/v1/me/player/play?device_id="
+ jukebox.sp_device.split("-")[1],
json={"uris": uri},
timeout=40,
headers={"Authorization": "Bearer " + jukebox.sp_access_token},
)
if r.status_code == 204:
return jsonify(jukebox_payment), HTTPStatus.OK
elif r.status_code == 401 or r.status_code == 403:
token = await api_get_token(juke_id)
if token == False:
return (
jsonify({"error": "Invoice not paid"}),
HTTPStatus.FORBIDDEN,
)
else:
return api_get_jukebox_invoice_paid(
song_id, juke_id, pay_hash
)
else:
return (
jsonify({"error": "Invoice not paid"}),
HTTPStatus.FORBIDDEN,
)
elif r.status_code == 200:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
r = await client.post( r = await client.post(
"https://api.spotify.com/v1/me/player/queue?uri=spotify%3Atrack%3A" "https://api.spotify.com/v1/me/player/queue?uri=spotify%3Atrack%3A"
+ queued[0] + song_id
+ "&device_id=" + "&device_id="
+ jukebox.sp_device.split("-")[1], + jukebox.sp_device.split("-")[1],
timeout=40, timeout=40,
headers={"Authorization": "Bearer " + jukebox.sp_access_token}, headers={"Authorization": "Bearer " + jukebox.sp_access_token},
) )
print(r) if r.status_code == 204:
else: return jsonify(jukebox_payment), HTTPStatus.OK
queued = queued[1:]
jukebox = await update_jukebox(juke_id=juke_id, queue=queued) elif r.status_code == 401 or r.status_code == 403:
queued = jukebox.queue token = await api_get_token(juke_id)
broadcast( if token == False:
json.dumps({"juke_id": juke_id, "queue": queued, "current": song}) return (
) jsonify({"error": "Invoice not paid"}),
jukebox = await update_jukebox(juke_id=juke_id, last_checked=time.time()) HTTPStatus.OK,
return jsonify(jukebox), HTTPStatus.OK )
else:
return await api_get_jukebox_invoice_paid(
song_id, juke_id, pay_hash
)
else:
return (
jsonify({"error": "Invoice not paid"}),
HTTPStatus.OK,
)
elif r.status_code == 401 or r.status_code == 403:
token = await api_get_token(juke_id)
if token == False:
return (
jsonify({"error": "Invoice not paid"}),
HTTPStatus.OK,
)
else:
return await api_get_jukebox_invoice_paid(
song_id, juke_id, pay_hash
)
return jsonify({"error": "Invoice not paid"}), HTTPStatus.OK
############################GET TRACKS ############################GET TRACKS
@ -277,32 +323,29 @@ async def api_get_jukebox_currently(juke_id):
timeout=40, timeout=40,
headers={"Authorization": "Bearer " + jukebox.sp_access_token}, headers={"Authorization": "Bearer " + jukebox.sp_access_token},
) )
try: if r.status_code == 204:
if r.json()["item"]: return jsonify({"error": "Nothing"}), HTTPStatus.OK
track = { elif r.status_code == 200:
"id": r.json()["item"]["id"], response = r.json()
"name": r.json()["item"]["name"], response["item"]
"album": r.json()["item"]["album"]["name"], track = {
"artist": r.json()["item"]["artists"][0]["name"], "id": response["item"]["id"],
"image": r.json()["item"]["album"]["images"][0]["url"], "name": response["item"]["name"],
} "album": response["item"]["album"]["name"],
"artist": response["item"]["artists"][0]["name"],
"image": response["item"]["album"]["images"][0]["url"],
}
return track, HTTPStatus.OK return track, HTTPStatus.OK
except AssertionError: elif r.status_code == 401:
something = None token = await api_get_token(juke_id)
try: if token == False:
if r.json()["error"]["status"] == 401: return (
token = await api_get_token(juke_id) jsonify({"error": "Invoice not paid"}),
if token == False: HTTPStatus.FORBIDDEN,
)
return jsonify({"error": "Something went wrong"}) else:
else: return await api_get_jukebox_currently(juke_id)
return await api_get_jukebox_currently(juke_id) else:
elif r.json()["error"]["status"] == 400: return jsonify("Something went wrong"), HTTPStatus.NOT_FOUND
return jsonify({"error": "Something went wrong"})
except ValueError:
return jsonify({"error": "Something went wrong"})
except AssertionError: except AssertionError:
something = None return jsonify("Something went wrong"), HTTPStatus.NOT_FOUND
return jsonify({"error": "Something went wrong"})
return jsonify({"error": "Something went wrong"})