From 2da68bf9747a508e1bba9a79e2cb25e759ba8208 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 14 Jun 2021 12:42:28 +0100 Subject: [PATCH 01/11] Update for jukebox extension readme removed the profit column showing 0 --- lnbits/extensions/jukebox/README.md | 35 +++++- lnbits/extensions/jukebox/static/js/index.js | 112 +++++++++---------- 2 files changed, 88 insertions(+), 59 deletions(-) diff --git a/lnbits/extensions/jukebox/README.md b/lnbits/extensions/jukebox/README.md index b92e7ea6f..c761db448 100644 --- a/lnbits/extensions/jukebox/README.md +++ b/lnbits/extensions/jukebox/README.md @@ -1,5 +1,36 @@ # Jukebox -To use this extension you need a Spotify client ID and client secret. You get these by creating an app in the Spotify developers dashboard here https://developer.spotify.com/dashboard/applications +## An actual Jukebox where users pay sats to play their favourite music from your playlists -Select the playlists you want people to be able to pay for, share the frontend page, profit :) +**Note:** To use this extension you need a Premium Spotify subscription. + +## Usage + +1. Click on "ADD SPOTIFY JUKEBOX"\ + ![add jukebox](https://i.imgur.com/NdVoKXd.png) +2. Follow the steps required on the form\ + + - give your jukebox a name + - select a wallet to receive payment + - define the price a user must pay to select a song\ + ![pick wallet price](https://i.imgur.com/4bJ8mb9.png) + - follow the steps to get your Spotify App and get the client ID and secret key\ + ![spotify keys](https://i.imgur.com/w2EzFtB.png) + - paste the codes in the form\ + ![api keys](https://i.imgur.com/6b9xauo.png) + - copy the _Redirect URL_ presented on the form\ + ![redirect url](https://i.imgur.com/GMzl0lG.png) + - on Spotify click the "EDIT SETTINGS" button and paste the copied link in the _Redirect URI's_ prompt + ![spotify app setting](https://i.imgur.com/vb0x4Tl.png) + - back on LNBits, click "AUTORIZE ACCESS" and "Agree" on the page that will open + - choose on which device the LNBits Jukebox extensions will stream to, you may have to be logged in in order to select the device (browser, smartphone app, etc...) + - and select what playlist will be available for users to choose songs (you need to have already playlist on Spotify)\ + ![select playlists](https://i.imgur.com/g4dbtED.png) + +3. After Jukebox is created, click the icon to open the dialog with the shareable QR, open the Jukebox page, etc...\ + ![shareable jukebox](https://i.imgur.com/EAh9PI0.png) +4. The users will see the Jukebox page and choose a song from the selected playlist\ + ![select song](https://i.imgur.com/YYjeQAs.png) +5. After selecting a song they'd like to hear next a dialog will show presenting the music\ + ![play for sats](https://i.imgur.com/eEHl3o8.png) +6. After payment, the song will automatically start playing on the device selected or enter the queue if some other music is already playing diff --git a/lnbits/extensions/jukebox/static/js/index.js b/lnbits/extensions/jukebox/static/js/index.js index 57f9c678f..1bf6ec4c8 100644 --- a/lnbits/extensions/jukebox/static/js/index.js +++ b/lnbits/extensions/jukebox/static/js/index.js @@ -46,12 +46,6 @@ new Vue({ align: 'left', label: 'Price', field: 'price' - }, - { - name: 'profit', - align: 'left', - label: 'Profit', - field: 'profit' } ], pagination: { @@ -81,7 +75,7 @@ new Vue({ }, computed: {}, methods: { - openQrCodeDialog: function (linkId) { + openQrCodeDialog: function(linkId) { var link = _.findWhere(this.JukeboxLinks, {id: linkId}) this.qrCodeDialog.data = _.clone(link) @@ -93,8 +87,12 @@ new Vue({ getJukeboxes() { self = this LNbits.api - .request('GET', '/jukebox/api/v1/jukebox', self.g.user.wallets[0].adminkey) - .then(function (response) { + .request( + 'GET', + '/jukebox/api/v1/jukebox', + self.g.user.wallets[0].adminkey + ) + .then(function(response) { self.JukeboxLinks = response.data.map(mapJukebox) }) .catch(err => { @@ -105,15 +103,15 @@ new Vue({ self = this LNbits.utils .confirmDialog('Are you sure you want to delete this Jukebox?') - .onOk(function () { + .onOk(function() { LNbits.api .request( 'DELETE', '/jukebox/api/v1/jukebox/' + juke_id, self.g.user.wallets[0].adminkey ) - .then(function (response) { - self.JukeboxLinks = _.reject(self.JukeboxLinks, function (obj) { + .then(function(response) { + self.JukeboxLinks = _.reject(self.JukeboxLinks, function(obj) { return obj.id === juke_id }) }) @@ -123,7 +121,7 @@ new Vue({ }) }) }, - updateJukebox: function (linkId) { + updateJukebox: function(linkId) { self = this var link = _.findWhere(self.JukeboxLinks, {id: linkId}) self.jukeboxDialog.data = _.clone(link._data) @@ -165,10 +163,10 @@ new Vue({ LNbits.utils.notifyApiError(err) }) }, - authAccess() { + authAccess() { self = this - self.requestAuthorization() - self.getSpotifyTokens() + self.requestAuthorization() + self.getSpotifyTokens() self.$q.notify({ spinner: true, message: 'Processing', @@ -178,7 +176,7 @@ new Vue({ getSpotifyTokens() { self = this var counter = 0 - var timerId = setInterval(function () { + var timerId = setInterval(function() { counter++ if (!self.jukeboxDialog.data.sp_user) { clearInterval(timerId) @@ -195,37 +193,37 @@ new Vue({ if (self.jukeboxDialog.data.sp_access_token) { self.refreshPlaylists() self.refreshDevices() - console.log("this.devices") + console.log('this.devices') console.log(self.devices) - console.log("this.devices") - setTimeout(function () { - if (self.devices.length < 1 || self.playlists.length < 1) { - self.$q.notify({ - spinner: true, - color: 'red', - message: - 'Error! Make sure Spotify is open on the device you wish to use, has playlists, and is playing something', - timeout: 10000 - }) - LNbits.api - .request( - 'DELETE', - '/jukebox/api/v1/jukebox/' + response.data.id, - self.g.user.wallets[0].adminkey - ) - .then(function (response) { - self.getJukeboxes() + console.log('this.devices') + setTimeout(function() { + if (self.devices.length < 1 || self.playlists.length < 1) { + self.$q.notify({ + spinner: true, + color: 'red', + message: + 'Error! Make sure Spotify is open on the device you wish to use, has playlists, and is playing something', + timeout: 10000 }) - .catch(err => { - LNbits.utils.notifyApiError(err) - }) - clearInterval(timerId) - self.closeFormDialog() - } else { - self.step = 4 - clearInterval(timerId) - } - }, 2000) + LNbits.api + .request( + 'DELETE', + '/jukebox/api/v1/jukebox/' + response.data.id, + self.g.user.wallets[0].adminkey + ) + .then(function(response) { + self.getJukeboxes() + }) + .catch(err => { + LNbits.utils.notifyApiError(err) + }) + clearInterval(timerId) + self.closeFormDialog() + } else { + self.step = 4 + clearInterval(timerId) + } + }, 2000) } } }) @@ -269,7 +267,7 @@ new Vue({ self.g.user.wallets[0].adminkey, self.jukeboxDialog.data ) - .then(function (response) { + .then(function(response) { console.log(response.data) if ( self.jukeboxDialog.data.sp_playlists && @@ -290,7 +288,7 @@ new Vue({ 'Bearer ' + this.jukeboxDialog.data.sp_access_token ) xhr.send(body) - xhr.onload = function () { + xhr.onload = function() { if (xhr.status == 401) { self.refreshAccessToken() self.playlistApi( @@ -326,7 +324,7 @@ new Vue({ 'Bearer ' + this.jukeboxDialog.data.sp_access_token ) xhr.send(body) - xhr.onload = function () { + xhr.onload = function() { if (xhr.status == 401) { self.refreshAccessToken() self.deviceApi( @@ -347,15 +345,15 @@ new Vue({ } } }, - refreshDevices() { + refreshDevices() { self = this - self.deviceApi( + self.deviceApi( 'GET', 'https://api.spotify.com/v1/me/player/devices', null ) }, - fetchAccessToken(code) { + fetchAccessToken(code) { self = this let body = 'grant_type=authorization_code' body += '&code=' + code @@ -363,16 +361,16 @@ new Vue({ '&redirect_uri=' + encodeURI(self.locationcbPath + self.jukeboxDialog.data.sp_id) - self.callAuthorizationApi(body) + self.callAuthorizationApi(body) }, - refreshAccessToken() { + refreshAccessToken() { self = this let body = 'grant_type=refresh_token' body += '&refresh_token=' + self.jukeboxDialog.data.sp_refresh_token body += '&client_id=' + self.jukeboxDialog.data.sp_user - self.callAuthorizationApi(body) + self.callAuthorizationApi(body) }, - callAuthorizationApi(body) { + callAuthorizationApi(body) { self = this console.log( btoa( @@ -394,7 +392,7 @@ new Vue({ ) ) xhr.send(body) - xhr.onload = function () { + xhr.onload = function() { let responseObj = JSON.parse(xhr.response) if (responseObj.access_token) { self.jukeboxDialog.data.sp_access_token = responseObj.access_token From d8be362ac4241e29bf699ce14fd8cbf7bb4edc14 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Fri, 18 Jun 2021 10:06:16 +0100 Subject: [PATCH 02/11] exclude package-lock.json form github --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ca3fcd001..79e10fb8c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ __pycache__ *$py.class .mypy_cache .vscode +*-lock.json *.egg *.egg-info From 26cf51c11cc2b0d1ca62969e9b96202a53ea2909 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Fri, 18 Jun 2021 10:06:45 +0100 Subject: [PATCH 03/11] removed polling and added listener behaviour --- lnbits/extensions/jukebox/__init__.py | 5 + lnbits/extensions/jukebox/tasks.py | 27 +++ .../jukebox/templates/jukebox/jukebox.html | 196 +++++++++--------- 3 files changed, 132 insertions(+), 96 deletions(-) create mode 100644 lnbits/extensions/jukebox/tasks.py diff --git a/lnbits/extensions/jukebox/__init__.py b/lnbits/extensions/jukebox/__init__.py index b6ec402f7..076ae4d9d 100644 --- a/lnbits/extensions/jukebox/__init__.py +++ b/lnbits/extensions/jukebox/__init__.py @@ -10,3 +10,8 @@ jukebox_ext: Blueprint = Blueprint( from .views_api import * # noqa from .views import * # noqa +from .tasks import register_listeners + +from lnbits.tasks import record_async + +jukebox_ext.record(record_async(register_listeners)) diff --git a/lnbits/extensions/jukebox/tasks.py b/lnbits/extensions/jukebox/tasks.py new file mode 100644 index 000000000..7c902937a --- /dev/null +++ b/lnbits/extensions/jukebox/tasks.py @@ -0,0 +1,27 @@ +import json +import trio # type: ignore + +from lnbits.core.models import Payment +from lnbits.core.crud import create_payment +from lnbits.core import db as core_db +from lnbits.tasks import register_invoice_listener, internal_invoice_paid +from lnbits.helpers import urlsafe_short_hash + +from .crud import get_jukebox, update_jukebox_payment + + +async def register_listeners(): + invoice_paid_chan_send, invoice_paid_chan_recv = trio.open_memory_channel(2) + register_invoice_listener(invoice_paid_chan_send) + await wait_for_paid_invoices(invoice_paid_chan_recv) + + +async def wait_for_paid_invoices(invoice_paid_chan: trio.MemoryReceiveChannel): + async for payment in invoice_paid_chan: + await on_invoice_paid(payment) + +async def on_invoice_paid(payment: Payment) -> None: + if "jukebox" != payment.extra.get("tag"): + # not a jukebox invoice + return + await update_jukebox_payment(payment.payment_hash, paid=True) diff --git a/lnbits/extensions/jukebox/templates/jukebox/jukebox.html b/lnbits/extensions/jukebox/templates/jukebox/jukebox.html index 7eeb5de35..d87681e36 100644 --- a/lnbits/extensions/jukebox/templates/jukebox/jukebox.html +++ b/lnbits/extensions/jukebox/templates/jukebox/jukebox.html @@ -9,7 +9,8 @@
- {{ currentPlay.name }}
+ {{ currentPlay.name }}
{{ currentPlay.artist }}
@@ -19,15 +20,30 @@

Pick a song

- +
- +