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

View File

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

View File

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

View File

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

View File

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

View File

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