diff --git a/lnbits/extensions/jukebox/crud.py b/lnbits/extensions/jukebox/crud.py index 01159c8eb..fc7ddca3b 100644 --- a/lnbits/extensions/jukebox/crud.py +++ b/lnbits/extensions/jukebox/crud.py @@ -20,8 +20,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) - 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, @@ -35,6 +35,7 @@ async def create_jukebox( sp_device, sp_playlists, int(price), + 0, ), ) jukebox = await get_jukebox(juke_id) @@ -42,17 +43,17 @@ async def create_jukebox( return jukebox -async def update_jukebox(id: str, **kwargs) -> Optional[Jukebox]: +async def update_jukebox(juke_id: str, **kwargs) -> Optional[Jukebox]: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) await db.execute( - f"UPDATE jukebox SET {q} WHERE id = ?", (*kwargs.values(), id) + f"UPDATE jukebox SET {q} WHERE id = ?", (*kwargs.values(), juke_id) ) - row = await db.fetchone("SELECT * FROM jukebox WHERE id = ?", (id,)) + row = await db.fetchone("SELECT * FROM jukebox WHERE id = ?", (juke_id,)) return Jukebox(**row) if row else None -async def get_jukebox(id: str) -> Optional[Jukebox]: - row = await db.fetchone("SELECT * FROM jukebox WHERE id = ?", (id,)) +async def get_jukebox(juke_id: str) -> Optional[Jukebox]: + row = await db.fetchone("SELECT * FROM jukebox WHERE id = ?", (juke_id,)) return Jukebox(**row) if row else None @@ -60,18 +61,20 @@ async def get_jukebox_by_user(user: str) -> Optional[Jukebox]: row = await db.fetchone("SELECT * FROM jukebox WHERE sp_user = ?", (user,)) return Jukebox(**row) if row else None + async def get_jukeboxs(user: str) -> List[Jukebox]: rows = await db.fetchall("SELECT * FROM jukebox WHERE user = ?", (user,)) for row in rows: - if not row.sp_playlists: + if row.sp_playlists == "": await delete_jukebox(row.id) - rows.remove(row) + rows = await db.fetchall("SELECT * FROM jukebox WHERE user = ?", (user,)) return [Jukebox.from_row(row) for row in rows] -async def delete_jukebox(id: str): + +async def delete_jukebox(juke_id: str): await db.execute( """ DELETE FROM jukebox WHERE id = ? """, - (id), + (juke_id), ) diff --git a/lnbits/extensions/jukebox/migrations.py b/lnbits/extensions/jukebox/migrations.py index 2f4e7afd7..9ca09a4a5 100644 --- a/lnbits/extensions/jukebox/migrations.py +++ b/lnbits/extensions/jukebox/migrations.py @@ -15,7 +15,8 @@ async def m001_initial(db): sp_refresh_token TEXT, sp_device TEXT, sp_playlists TEXT, - price INTEGER + price INTEGER, + profit INTEGER ); """ ) diff --git a/lnbits/extensions/jukebox/models.py b/lnbits/extensions/jukebox/models.py index f66219b16..f869de4f8 100644 --- a/lnbits/extensions/jukebox/models.py +++ b/lnbits/extensions/jukebox/models.py @@ -19,6 +19,7 @@ class Jukebox(NamedTuple): sp_device: str sp_playlists: str price: int + profit: int @classmethod def from_row(cls, row: Row) -> "Jukebox": diff --git a/lnbits/extensions/jukebox/static/js/index.js b/lnbits/extensions/jukebox/static/js/index.js index 3ec79f872..fde881374 100644 --- a/lnbits/extensions/jukebox/static/js/index.js +++ b/lnbits/extensions/jukebox/static/js/index.js @@ -4,13 +4,13 @@ Vue.component(VueQrcode.name, VueQrcode) var mapJukebox = obj => { obj._data = _.clone(obj) - - obj.device = obj.sp_device.split("-")[0] - playlists = obj.sp_playlists.split(",") - var i; + obj.sp_id = obj.id + obj.device = obj.sp_device.split('-')[0] + playlists = obj.sp_playlists.split(',') + var i playlistsar = [] for (i = 0; i < playlists.length; i++) { - playlistsar.push(playlists[i].split("-")[0]) + playlistsar.push(playlists[i].split('-')[0]) } obj.playlist = playlistsar.join() return obj @@ -47,6 +47,12 @@ new Vue({ label: 'Price', field: 'price' }, + { + name: 'profit', + align: 'left', + label: 'Profit', + field: 'profit' + } ], pagination: { rowsPerPage: 10 @@ -60,8 +66,8 @@ new Vue({ playlists: [], JukeboxLinks: [], step: 1, - locationcbPath: "", - locationcb: "", + locationcbPath: '', + locationcb: '', jukeboxDialog: { show: false, data: {} @@ -69,90 +75,132 @@ new Vue({ spotifyDialog: false } }, - computed: { - - }, + computed: {}, methods: { - getJukeboxes(){ + getJukeboxes() { self = this LNbits.api - .request('GET', '/jukebox/api/v1/jukebox', self.g.user.wallets[0].inkey) - .then(function (response) { - self.JukeboxLinks = response.data.map(mapJukebox) - }) - .catch(err => { - LNbits.utils.notifyApiError(err) - }) - }, - deleteJukebox(juke_id){ - self = this - 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) { - return obj.id === juke_id + .request('GET', '/jukebox/api/v1/jukebox', self.g.user.wallets[0].inkey) + .then(function (response) { + self.JukeboxLinks = response.data.map(mapJukebox) }) - }) + .catch(err => { + LNbits.utils.notifyApiError(err) + }) + }, + deleteJukebox(juke_id) { + self = this + LNbits.utils + .confirmDialog('Are you sure you want to delete this Jukebox?') + .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) { + return obj.id === juke_id + }) + }) - .catch(err => { - LNbits.utils.notifyApiError(err) - }) + .catch(err => { + LNbits.utils.notifyApiError(err) + }) + }) + }, + updateJukebox: function (linkId) { + self = this + var link = _.findWhere(self.JukeboxLinks, {id: linkId}) + self.jukeboxDialog.data = _.clone(link._data) + console.log(this.jukeboxDialog.data.sp_access_token) + + self.refreshDevices() + self.refreshPlaylists() + + self.step = 4 + self.jukeboxDialog.data.sp_device = [] + self.jukeboxDialog.data.sp_playlists = [] + self.jukeboxDialog.data.sp_id = self.jukeboxDialog.data.id + self.jukeboxDialog.show = true }, closeFormDialog() { this.jukeboxDialog.data = {} this.jukeboxDialog.show = false this.step = 1 }, - submitSpotify() { + submitSpotifyKeys() { self = this self.jukeboxDialog.data.user = self.g.user.id - self.requestAuthorization() - this.$q.notify({ - spinner: true, - message: 'Fetching token', - timeout: 4000 - }) - LNbits.api.request( + + LNbits.api + .request( 'POST', '/jukebox/api/v1/jukebox/', self.g.user.wallets[0].adminkey, self.jukeboxDialog.data ) .then(response => { - if(response.data){ - var timerId = setInterval(function(){ - if(!self.jukeboxDialog.data.sp_user){ - clearInterval(timerId); - } + if (response.data) { self.jukeboxDialog.data.sp_id = response.data.id - LNbits.api - .request('GET', '/jukebox/api/v1/jukebox/spotify/' + self.jukeboxDialog.data.sp_id, self.g.user.wallets[0].inkey) - .then(response => { - if(response.data.sp_access_token){ - self.jukeboxDialog.data.sp_access_token = response.data.sp_access_token - self.step = 3 - self.fetchAccessToken() - clearInterval(timerId) - } - }) - .catch(err => { - LNbits.utils.notifyApiError(err) - }) - }, 3000) - } + self.step = 3 + } }) .catch(err => { - LNbits.utils.notifyApiError(err) + LNbits.utils.notifyApiError(err) }) }, - requestAuthorization(){ + authAccess() { + self = this + self.requestAuthorization() + self.$q.notify({ + spinner: true, + message: 'Fetching token', + timeout: 4000 + }) + self.getSpotifyTokens() + }, + getSpotifyTokens() { + self = this + + var timerId = setInterval(function () { + if (!self.jukeboxDialog.data.sp_user) { + clearInterval(timerId) + } + LNbits.api + .request( + 'GET', + '/jukebox/api/v1/jukebox/spotify/' + self.jukeboxDialog.data.sp_id, + self.g.user.wallets[0].inkey + ) + .then(response => { + if (response.data.sp_access_token) { + self.fetchAccessToken(response.data.sp_access_token) + if (self.jukeboxDialog.data.sp_access_token) { + self.refreshPlaylists() + self.refreshDevices() + self.step = 4 + clearInterval(timerId) + } + } + }) + .catch(err => { + LNbits.utils.notifyApiError(err) + }) + }, 5000) + }, + requestAuthorization() { self = this var url = 'https://accounts.spotify.com/authorize' url += '?client_id=' + self.jukeboxDialog.data.sp_user url += '&response_type=code' - url += '&redirect_uri=' + encodeURI(self.locationcbPath) + self.jukeboxDialog.data.sp_user - url += "&show_dialog=true" - url += '&scope=user-read-private user-read-email user-modify-playback-state user-read-playback-position user-library-read streaming user-read-playback-state user-read-recently-played playlist-read-private' + url += + '&redirect_uri=' + + encodeURI(self.locationcbPath + self.jukeboxDialog.data.sp_id) + url += '&show_dialog=true' + url += + '&scope=user-read-private user-read-email user-modify-playback-state user-read-playback-position user-library-read streaming user-read-playback-state user-read-recently-played playlist-read-private' window.open(url) }, @@ -160,15 +208,12 @@ new Vue({ this.jukeboxDialog.show = true this.jukeboxDialog.data = {} }, - openUpdateDialog(itemId) { - this.jukeboxDialog.show = true - let item = this.jukebox.items.find(item => item.id === itemId) - this.jukeboxDialog.data = item - }, - createJukebox(){ + createJukebox() { self = this + console.log(this.jukeboxDialog.data) this.jukeboxDialog.data.sp_playlists = this.jukeboxDialog.data.sp_playlists.join() - LNbits.api.request( + LNbits.api + .request( 'PUT', '/jukebox/api/v1/jukebox/' + this.jukeboxDialog.data.sp_id, self.g.user.wallets[0].adminkey, @@ -179,90 +224,118 @@ new Vue({ self.jukeboxDialog.show = false }) }, - - playlistApi(method, url, body){ + playlistApi(method, url, body) { self = this let xhr = new XMLHttpRequest() xhr.open(method, url, true) xhr.setRequestHeader('Content-Type', 'application/json') - xhr.setRequestHeader('Authorization', 'Bearer ' + this.jukeboxDialog.data.sp_access_token) + xhr.setRequestHeader( + 'Authorization', + 'Bearer ' + this.jukeboxDialog.data.sp_access_token + ) xhr.send(body) - xhr.onload = function() { + xhr.onload = function () { let responseObj = JSON.parse(xhr.response) + self.jukeboxDialog.data.playlists = null self.playlists = [] - var i; + self.jukeboxDialog.data.playlists = [] + var i for (i = 0; i < responseObj.items.length; i++) { - self.playlists.push(responseObj.items[i].name + "-" + responseObj.items[i].id) + self.playlists.push( + responseObj.items[i].name + '-' + responseObj.items[i].id + ) } } }, - refreshPlaylists(){ + refreshPlaylists() { self = this - self.playlistApi( "GET", "https://api.spotify.com/v1/me/playlists", null) + self.playlistApi('GET', 'https://api.spotify.com/v1/me/playlists', null) }, - deviceApi(method, url, body){ + deviceApi(method, url, body) { self = this let xhr = new XMLHttpRequest() xhr.open(method, url, true) xhr.setRequestHeader('Content-Type', 'application/json') - xhr.setRequestHeader('Authorization', 'Bearer ' + this.jukeboxDialog.data.sp_access_token) + xhr.setRequestHeader( + 'Authorization', + 'Bearer ' + this.jukeboxDialog.data.sp_access_token + ) xhr.send(body) - xhr.onload = function() { + xhr.onload = function () { let responseObj = JSON.parse(xhr.response) - self.devices = [] - var i; - for (i = 0; i < responseObj.devices.length; i++) { - self.devices.push(responseObj.devices[i].name + "-" + responseObj.devices[i].id) + self.jukeboxDialog.data.devices = [] + self.devices = [] + var i + for (i = 0; i < responseObj.devices.length; i++) { + self.devices.push( + responseObj.devices[i].name + '-' + responseObj.devices[i].id + ) } - } }, - refreshDevices(){ + refreshDevices() { self = this - self.deviceApi( "GET", "https://api.spotify.com/v1/me/player/devices", null) + self.deviceApi( + 'GET', + 'https://api.spotify.com/v1/me/player/devices', + null + ) }, - fetchAccessToken( ){ + fetchAccessToken(code) { self = this - let body = "grant_type=authorization_code" - body += "&code=" + self.jukeboxDialog.data.sp_access_token - body += '&redirect_uri=' + encodeURI(self.locationcbPath) + self.jukeboxDialog.data.sp_user + let body = 'grant_type=authorization_code' + body += '&code=' + code + body += + '&redirect_uri=' + + encodeURI(self.locationcbPath + self.jukeboxDialog.data.sp_id) + this.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 + let body = 'grant_type=refresh_token' + body += '&refresh_token=' + self.jukeboxDialog.data.sp_refresh_token + body += '&client_id=' + self.jukeboxDialog.data.sp_user this.callAuthorizationApi(body) }, - callAuthorizationApi(body){ + callAuthorizationApi(body) { self = this let xhr = new XMLHttpRequest() - xhr.open("POST", "https://accounts.spotify.com/api/token", true) + xhr.open('POST', 'https://accounts.spotify.com/api/token', true) xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') - xhr.setRequestHeader('Authorization', 'Basic ' + btoa(this.jukeboxDialog.data.sp_user + ":" + this.jukeboxDialog.data.sp_secret)) + xhr.setRequestHeader( + 'Authorization', + 'Basic ' + + btoa( + this.jukeboxDialog.data.sp_user + + ':' + + this.jukeboxDialog.data.sp_secret + ) + ) xhr.send(body) - xhr.onload = function() { + xhr.onload = function () { let responseObj = JSON.parse(xhr.response) - self.jukeboxDialog.data.sp_access_token = responseObj.access_token - self.jukeboxDialog.data.sp_refresh_token = responseObj.refresh_token - self.refreshPlaylists() - self.refreshDevices() + if (responseObj.access_token) { + self.jukeboxDialog.data.sp_access_token = responseObj.access_token + self.jukeboxDialog.data.sp_refresh_token = responseObj.refresh_token + console.log(responseObj) + } } - }, + } }, created() { - var getJukeboxes = this.getJukeboxes getJukeboxes() this.selectedWallet = this.g.user.wallets[0] - this.locationcbPath = String([ - window.location.protocol, - '//', - window.location.host, - '/jukebox/api/v1/jukebox/spotify/cb/' - ].join('')) + this.locationcbPath = String( + [ + window.location.protocol, + '//', + window.location.host, + '/jukebox/api/v1/jukebox/spotify/cb/' + ].join('') + ) this.locationcb = this.locationcbPath } }) diff --git a/lnbits/extensions/jukebox/templates/jukebox/index.html b/lnbits/extensions/jukebox/templates/jukebox/index.html index c865794bf..1b0a59498 100644 --- a/lnbits/extensions/jukebox/templates/jukebox/index.html +++ b/lnbits/extensions/jukebox/templates/jukebox/index.html @@ -58,6 +58,14 @@ + here.
- In the app go to edit-settings, set the redirect URI to this link - (replacing the CLIENT-ID with your own) {% raw %}{{ locationcb - }}CLIENT-ID{% endraw %} + >. Get token
Submit keys Get tokenSubmit keys + +
+ Cancel +
+ + +
+ + + + In the app go to edit-settings, set the redirect URI to this link +
{% raw %}{{ locationcb }}{{ jukeboxDialog.data.sp_id }}{% endraw + %} + +
+
+ Authorise access + Authorise access
@@ -223,11 +260,11 @@ ", methods=["GET"]) -async def api_check_credentials_callbac(sp_user): +@jukebox_ext.route("/api/v1/jukebox/spotify/cb/", methods=["GET"]) +async def api_check_credentials_callbac(juke_id): + print(request.args) sp_code = "" sp_access_token = "" sp_refresh_token = "" - print(request.args) - jukebox = await get_jukebox_by_user(sp_user) + jukebox = await get_jukebox(juke_id) if request.args.get("code"): sp_code = request.args.get("code") jukebox = await update_jukebox( - sp_user=sp_user, sp_secret=jukebox.sp_secret, sp_access_token=sp_code + juke_id=juke_id, sp_secret=jukebox.sp_secret, sp_access_token=sp_code ) if request.args.get("access_token"): sp_access_token = request.args.get("access_token") sp_refresh_token = request.args.get("refresh_token") jukebox = await update_jukebox( - sp_user=sp_user, + juke_id=juke_id, sp_secret=jukebox.sp_secret, sp_access_token=sp_access_token, sp_refresh_token=sp_refresh_token, @@ -70,7 +65,7 @@ async def api_check_credentials_check(sp_id): @jukebox_ext.route("/api/v1/jukebox/", methods=["POST"]) -@jukebox_ext.route("/api/v1/jukebox/", methods=["PUT"]) +@jukebox_ext.route("/api/v1/jukebox/", methods=["PUT"]) @api_check_wallet_key("admin") @api_validate_post_request( schema={ @@ -86,9 +81,9 @@ async def api_check_credentials_check(sp_id): "price": {"type": "string", "required": True}, } ) -async def api_create_update_jukebox(item_id=None): - if item_id: - jukebox = await update_jukebox(item_id, **g.data) +async def api_create_update_jukebox(juke_id=None): + if juke_id: + jukebox = await update_jukebox(juke_id=juke_id, **g.data) else: jukebox = await create_jukebox(**g.data) return jsonify(jukebox._asdict()), HTTPStatus.CREATED @@ -101,12 +96,7 @@ async def api_delete_item(juke_id): try: return ( jsonify( - [ - { - **jukebox._asdict() - } - for jukebox in await get_jukeboxs(g.wallet.user) - ] + [{**jukebox._asdict()} for jukebox in await get_jukeboxs(g.wallet.user)] ), HTTPStatus.OK, )