Can connect to spotify ccount

This commit is contained in:
Ben Arc 2021-04-29 00:40:58 +01:00
parent dc10a0f52b
commit b594281f83
7 changed files with 137 additions and 61 deletions

View file

@ -11,15 +11,16 @@ async def create_jukebox(
price: int, price: int,
sp_user: str, sp_user: str,
sp_secret: str, sp_secret: str,
sp_token: Optional[str] = "", sp_access_token: Optional[str] = "",
sp_refresh_token: Optional[str] = "",
sp_device: Optional[str] = "", sp_device: Optional[str] = "",
sp_playlists: Optional[str] = "", sp_playlists: Optional[str] = "",
) -> Jukebox: ) -> Jukebox:
juke_id = urlsafe_short_hash() juke_id = urlsafe_short_hash()
result = await db.execute( result = await db.execute(
""" """
INSERT INTO jukebox (id, title, wallet, sp_user, sp_secret, sp_token, sp_device, sp_playlists, price) INSERT INTO jukebox (id, title, wallet, sp_user, sp_secret, sp_access_token, sp_refresh_token, sp_device, sp_playlists, price)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", """,
( (
juke_id, juke_id,
@ -27,7 +28,8 @@ async def create_jukebox(
wallet, wallet,
sp_user, sp_user,
sp_secret, sp_secret,
sp_token, sp_access_token,
sp_refresh_token,
sp_device, sp_device,
sp_playlists, sp_playlists,
int(price), int(price),

View file

@ -10,7 +10,8 @@ async def m001_initial(db):
wallet TEXT, wallet TEXT,
sp_user TEXT NOT NULL, sp_user TEXT NOT NULL,
sp_secret TEXT NOT NULL, sp_secret TEXT NOT NULL,
sp_token TEXT, sp_access_token TEXT,
sp_refresh_token TEXT,
sp_device TEXT, sp_device TEXT,
sp_playlists TEXT, sp_playlists TEXT,
price INTEGER price INTEGER

View file

@ -13,7 +13,8 @@ class Jukebox(NamedTuple):
wallet: str wallet: str
sp_user: str sp_user: str
sp_secret: str sp_secret: str
sp_token: str sp_access_token: str
sp_refresh_token: str
sp_device: str sp_device: str
sp_playlists: str sp_playlists: str
price: int price: int

View file

@ -13,11 +13,12 @@ new Vue({
return { return {
isPwd: true, isPwd: true,
tokenFetched: true, tokenFetched: true,
device: [], devices: [],
jukebox: {}, jukebox: {},
playlists: [], playlists: [],
step: 1, step: 1,
locationcbPath: "", locationcbPath: "",
locationcb: "",
jukeboxDialog: { jukeboxDialog: {
show: false, show: false,
data: {} data: {}
@ -55,22 +56,26 @@ new Vue({
.then(response => { .then(response => {
if(response.data){ if(response.data){
var timerId = setInterval(function(){ var timerId = setInterval(function(){
console.log(response.data)
if(!self.jukeboxDialog.data.sp_user){ if(!self.jukeboxDialog.data.sp_user){
clearInterval(timerId); clearInterval(timerId);
} }
self.jukeboxDialog.data.sp_id = response.data.id
LNbits.api LNbits.api
.request('GET', '/jukebox/api/v1/jukebox/spotify/' + self.jukeboxDialog.data.sp_user, self.g.user.wallets[0].inkey) .request('GET', '/jukebox/api/v1/jukebox/spotify/' + self.jukeboxDialog.data.sp_id, self.g.user.wallets[0].inkey)
.then(response => { .then(response => {
if(response.data.sp_token){ if(response.data.sp_access_token){
console.log(response.data.sp_token) self.jukeboxDialog.data.sp_access_token = response.data.sp_access_token
self.step = 3 self.step = 3
clearInterval(timerId); self.fetchAccessToken()
self.refreshPlaylists()
self.$q.notify({ clearInterval(timerId)
message: 'Success! App is now linked!',
timeout: 3000 // self.refreshPlaylists(response.data.sp_token)
}) // self.$q.notify({
// message: 'Success! App is now linked!',
// timeout: 3000
// })
//set devices, playlists //set devices, playlists
} }
}) })
@ -86,13 +91,12 @@ new Vue({
}, },
requestAuthorization(){ requestAuthorization(){
self = this self = this
let url = 'https://accounts.spotify.com/authorize' var url = 'https://accounts.spotify.com/authorize'
url += '?scope=user-modify-playback-state%20user-read-playback-position' url += '?client_id=' + self.jukeboxDialog.data.sp_user
url += '%20user-library-read%20streaming%20user-read-playback-state' url += '&response_type=code'
url += '%20user-read-recently-played%20playlist-read-private&response_type=code' url += '&redirect_uri=' + encodeURI(self.locationcbPath + self.jukeboxDialog.data.sp_user)
url += '&redirect_uri=' + encodeURIComponent(self.locationcbPath) + self.jukeboxDialog.data.sp_user url += "&show_dialog=true"
url += '&client_id=' + self.jukeboxDialog.data.sp_user 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 += '&show_dialog=true'
console.log(url) console.log(url)
window.open(url) window.open(url)
}, },
@ -105,47 +109,96 @@ new Vue({
let item = this.jukebox.items.find(item => item.id === itemId) let item = this.jukebox.items.find(item => item.id === itemId)
this.jukeboxDialog.data = item this.jukeboxDialog.data = item
}, },
createJukebox(){
self = this
callApi(method, url, body, callback){ LNbits.api.request(
'PUT',
'/jukebox/api/v1/jukebox/' + this.jukeboxDialog.data.sp_id,
self.g.user.wallets[0].adminkey,
self.jukeboxDialog.data
)
.then(response => {
console.log(response.data)
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
},
playlistApi(method, url, body){
self = this
let xhr = new XMLHttpRequest() let xhr = new XMLHttpRequest()
xhr.open(method, url, true) xhr.open(method, url, true)
xhr.setRequestHeader('Content-Type', 'application/json') xhr.setRequestHeader('Content-Type', 'application/json')
xhr.setRequestHeader('Authorization', 'Bearer ' + self.jukeboxDialog.data.sp_token) xhr.setRequestHeader('Authorization', 'Bearer ' + this.jukeboxDialog.data.sp_access_token)
xhr.send(body) xhr.send(body)
xhr.onload = callback xhr.onload = function() {
let responseObj = JSON.parse(xhr.response)
console.log(responseObj.items)
var i;
for (i = 0; i < responseObj.items.length; i++) {
self.playlists.push(responseObj.items[i].name + "-" + responseObj.items[i].id)
}
}
}, },
refreshPlaylists(){ refreshPlaylists(){
console.log("sdfvasdv") self = this
callApi( "GET", "https://api.spotify.com/v1/me/playlists", null, handlePlaylistsResponse ) self.playlistApi( "GET", "https://api.spotify.com/v1/me/playlists", null)
}, },
handlePlaylistsResponse(){ deviceApi(method, url, body){
console.log("data") self = this
if ( this.status == 200 ){ let xhr = new XMLHttpRequest()
var data = JSON.parse(this.responseText) xhr.open(method, url, true)
console.log(data) xhr.setRequestHeader('Content-Type', 'application/json')
} xhr.setRequestHeader('Authorization', 'Bearer ' + this.jukeboxDialog.data.sp_access_token)
else if ( this.status == 401 ){ xhr.send(body)
refreshAccessToken() xhr.onload = function() {
} let responseObj = xhr.response
else { alert(responseObj)
console.log(this.responseText) var i;
alert(this.responseText) for (i = 0; i < responseObj.items.length; i++) {
self.devices.push(responseObj.items[i].name + "-" + responseObj.items[i].id)
}
} }
}, },
refreshDevices(){
self = this
self.deviceApi( "GET", "https://api.spotify.com/v1/me/player/devices", null)
},
fetchAccessToken( ){
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)
this.callAuthorizationApi(body)
},
refreshAccessToken(){ refreshAccessToken(){
refresh_token = localStorage.getItem("refresh_token") self = this
let body = "grant_type=refresh_token" let body = "grant_type=refresh_token"
body += "&refresh_token=" + self.jukeboxDialog.data.sp_token body += "&refresh_token=" + self.jukeboxDialog.data.sp_refresh_token
body += "&client_id=" + self.jukeboxDialog.data.sp_user body += "&client_id=" + self.jukeboxDialog.data.sp_user
callAuthorizationApi(body) this.callAuthorizationApi(body)
}, },
callAuthorizationApi(body){ callAuthorizationApi(body){
self = this
let xhr = new XMLHttpRequest() let xhr = new XMLHttpRequest()
xhr.open("POST", TOKEN, true) xhr.open("POST", "https://accounts.spotify.com/api/token", true)
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
xhr.setRequestHeader('Authorization', 'Basic ' + btoa(self.jukeboxDialog.data.sp_user + ":" + self.jukeboxDialog.data.sp_secret)) xhr.setRequestHeader('Authorization', 'Basic ' + btoa(this.jukeboxDialog.data.sp_user + ":" + this.jukeboxDialog.data.sp_secret))
xhr.send(body) xhr.send(body)
xhr.onload = handleAuthorizationResponse console.log(('Authorization', 'Basic ' + btoa(this.jukeboxDialog.data.sp_user + ":" + this.jukeboxDialog.data.sp_secret)))
xhr.onload = function() {
let responseObj = JSON.parse(xhr.response)
alert(responseObj.access_token)
alert(responseObj.refresh_token)
self.jukeboxDialog.data.sp_access_token = responseObj.access_token
self.jukeboxDialog.data.sp_refresh_token = responseObj.refresh_token
console.log(self.jukeboxDialog.data)
self.refreshPlaylists()
self.refreshDevices()
}
}, },
}, },
created() { created() {
@ -156,5 +209,7 @@ new Vue({
window.location.host, window.location.host,
'/jukebox/api/v1/jukebox/spotify/cb/' '/jukebox/api/v1/jukebox/spotify/cb/'
].join('')) ].join(''))
console.log(this.locationcbPath)
this.locationcb = this.locationcbPath
} }
}) })

View file

@ -174,7 +174,7 @@
>here</a >here</a
>. <br /> >. <br />
In the app go to edit-settings, set the redirect URI to this link In the app go to edit-settings, set the redirect URI to this link
(replacing the CLIENT-ID with your own) {% raw %}{{ locationcbPath (replacing the CLIENT-ID with your own) {% raw %}{{ locationcb
}}CLIENT-ID{% endraw %} }}CLIENT-ID{% endraw %}
<q-input <q-input
filled filled
@ -238,13 +238,14 @@
dense dense
emit-value emit-value
v-model="jukeboxDialog.data.sp_device" v-model="jukeboxDialog.data.sp_device"
:options="device" :options="devices"
label="Device jukebox will play to" label="Device jukebox will play to"
></q-select> ></q-select>
<q-select <q-select
class="q-pb-md" class="q-pb-md"
filled filled
dense dense
multiple
emit-value emit-value
v-model="jukeboxDialog.data.sp_playlists" v-model="jukeboxDialog.data.sp_playlists"
:options="playlists" :options="playlists"
@ -252,7 +253,9 @@
></q-select> ></q-select>
<div class="row q-mt-md"> <div class="row q-mt-md">
<div class="col-5"> <div class="col-5">
<q-btn color="green-7" @click="step = 2">Create Jukebox</q-btn> <q-btn color="green-7" @click="createJukebox"
>Create Jukebox</q-btn
>
</div> </div>
<div class="col-7"> <div class="col-7">
<q-btn <q-btn

View file

@ -9,7 +9,7 @@ from lnbits.core.crud import get_standalone_payment
from . import jukebox_ext from . import jukebox_ext
from .crud import get_jukebox from .crud import get_jukebox
from urllib.parse import unquote
@jukebox_ext.route("/") @jukebox_ext.route("/")
@validate_uuids(["usr"], required=True) @validate_uuids(["usr"], required=True)
@ -22,4 +22,4 @@ async def index():
async def print_qr_codes(juke_id): async def print_qr_codes(juke_id):
jukebox = await get_jukebox(juke_id) jukebox = await get_jukebox(juke_id)
return await render_template("jukebox/jukebox.html", jukebox=jukebox) return await render_template("jukebox/jukebox.html", jukebox=jukebox)

View file

@ -25,18 +25,30 @@ async def api_get_jukeboxs():
##################SPOTIFY AUTH##################### ##################SPOTIFY AUTH#####################
@jukebox_ext.route("/api/v1/jukebox/spotify/cb/<sp_user>/", methods=["GET"]) @jukebox_ext.route("/api/v1/jukebox/spotify/cb/<sp_user>", methods=["GET"])
async def api_check_credentials_callbac(sp_user): async def api_check_credentials_callbac(sp_user):
sp_code = ""
sp_access_token = ""
sp_refresh_token = ""
print(request.args)
jukebox = await get_jukebox_by_user(sp_user) jukebox = await get_jukebox_by_user(sp_user)
jukebox = await update_jukebox( if request.args.get('code'):
sp_user=sp_user, sp_secret=jukebox.sp_secret, sp_token=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
)
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, sp_secret=jukebox.sp_secret, sp_access_token=sp_access_token, sp_refresh_token=sp_refresh_token
)
return "<h1>Success!</h1><h2>You can close this window</h2>" return "<h1>Success!</h1><h2>You can close this window</h2>"
@jukebox_ext.route("/api/v1/jukebox/spotify/<sp_user>", methods=["GET"]) @jukebox_ext.route("/api/v1/jukebox/spotify/<sp_id>", methods=["GET"])
@api_check_wallet_key("invoice") @api_check_wallet_key("invoice")
async def api_check_credentials_check(sp_user): async def api_check_credentials_check(sp_id):
jukebox = await get_jukebox_by_user(sp_user) jukebox = await get_jukebox(sp_id)
return jsonify(jukebox._asdict()), HTTPStatus.CREATED return jsonify(jukebox._asdict()), HTTPStatus.CREATED
@ -49,14 +61,16 @@ async def api_check_credentials_check(sp_user):
"wallet": {"type": "string", "empty": False, "required": True}, "wallet": {"type": "string", "empty": False, "required": True},
"sp_user": {"type": "string", "empty": False, "required": True}, "sp_user": {"type": "string", "empty": False, "required": True},
"sp_secret": {"type": "string", "required": True}, "sp_secret": {"type": "string", "required": True},
"sp_token": {"type": "string", "required": False}, "sp_access_token": {"type": "string", "required": False},
"sp_refresh_token": {"type": "string", "required": False},
"sp_device": {"type": "string", "required": False}, "sp_device": {"type": "string", "required": False},
"sp_playlists": {"type": "string", "required": False}, "sp_playlists": {"type": "string", "required": False},
"price": {"type": "string", "required": True}, "price": {"type": "string", "required": True},
} }
) )
async def api_create_update_jukebox(item_id=None): async def api_create_update_jukebox(item_id=None):
print(g.data) if item_id:
jukebox = await update_jukebox(**g.data)
jukebox = await create_jukebox(**g.data) jukebox = await create_jukebox(**g.data)
return jsonify(jukebox._asdict()), HTTPStatus.CREATED return jsonify(jukebox._asdict()), HTTPStatus.CREATED