let spotifyCredentials | |
let widget = await createWidget() | |
Script.setWidget(widget) | |
Script.complete() | |
async function createWidget() { | |
let widget = new ListWidget() | |
let spotifyIcon = await getImage("spotify-icon.png") | |
widget.backgroundColor = new Color("1e2040") | |
// load spotify credentials from iCloud Drive | |
spotifyCredentials = await loadSpotifyCredentials() | |
if(spotifyCredentials != null) { | |
widget.url = "spotify://" | |
let nowPlaying = await loadNowPlaying() | |
if(nowPlaying != null) { | |
widget.setPadding(20,12, 8, 8) | |
let cleanTitle = nowPlaying.item.name.split(" (")[0] | |
cleanTitle = cleanTitle.split(" - ")[0] | |
const artist = nowPlaying.item.artists[0].name | |
// console.log("Now Playing: " + cleanTitle + " - " + artist) | |
// cover art | |
const coverUrl = nowPlaying.item.album.images[0].url | |
let coverImage = await loadImage(coverUrl) | |
let row = widget.addStack() | |
let stack = row.addStack() | |
stack.layoutHorizontally() | |
stack.size = new Size(105,105) | |
let cover = stack.addImage(coverImage) | |
cover.cornerRadius = 6 | |
cover.borderColor = new Color("#1DB954") | |
cover.borderWidth = 3 | |
stack.addSpacer(10) | |
let stack2 = row.addStack() | |
stack2.layoutVertically() | |
let spotifyIconImage = stack2.addImage(spotifyIcon) | |
stack2.addSpacer(10) | |
let shuffleIcon = await getImage("shuffle-icon.png") | |
let shuffleIconImage = stack2.addImage(shuffleIcon) | |
if(nowPlaying.shuffle_state == true) { | |
shuffleIconImage.imageOpacity = 1 | |
} else { | |
shuffleIconImage.imageOpacity = 0.3 | |
} | |
let repeatIcon = await getImage("repeat-icon.png") | |
stack2.addSpacer(10) | |
let repeatIconImage = stack2.addImage(repeatIcon) | |
if(nowPlaying.repeat_state === "off") { | |
repeatIconImage.imageOpacity = 0.3 | |
} else { | |
repeatIconImage.imageOpacity = 1.0 | |
} | |
// add title and artist | |
let titleTxt = widget.addText(cleanTitle) | |
titleTxt.font = Font.semiboldSystemFont(11) | |
titleTxt.textColor = Color.white() | |
titleTxt.lineLimit = 1 | |
widget.addSpacer(2) | |
let artistTxt = widget.addText(artist) | |
artistTxt.font = Font.boldSystemFont(11) | |
artistTxt.textColor = new Color("#1DB954") | |
artistTxt.lineLimit = 1 | |
widget.addSpacer() | |
} else { | |
// Spotify playback stopped | |
let spotifyImage = widget.addImage(spotifyIcon) | |
spotifyImage.imageSize = new Size(25,25) | |
spotifyImage.rightAlignImage() | |
widget.addSpacer(10) | |
let offIcon = await getImage("offline-icon.png") | |
let offImage = widget.addImage(offIcon) | |
offImage.imageSize = new Size(50,50) | |
offImage.centerAlignImage() | |
widget.addSpacer(5) | |
let playbackText = widget.addText("Playback stopped") | |
playbackText.font = Font.semiboldSystemFont(11) | |
playbackText.textColor = Color.white() | |
playbackText.centerAlignText() | |
widget.addSpacer() | |
} | |
} else { | |
// no credentials found | |
let spotifyImage = widget.addImage(spotifyIcon) | |
spotifyImage.imageSize = new Size(25,25) | |
spotifyImage.rightAlignImage() | |
widget.addSpacer(10) | |
console.log("Could not find Spotify credentials!") | |
let ts = widget.addText("Couldn't find your spotify credentials in iCloud Drive. \n\n Please tap me for setup instructions.") | |
ts.textColor = Color.white() | |
ts.font = Font.boldSystemFont(11) | |
ts.leftAlignText() | |
widget.url = "https://gist.github.com/marco79cgn/79a6a265d978dc22cc2a12058b24e02b#gistcomment-3469230" | |
} | |
return widget | |
} | |
// get nowPlaying via Spotify Web API | |
async function loadNowPlaying() { | |
const req = new Request("https://api.spotify.com/v1/me/player") | |
req.headers = { "Authorization": "Bearer " + spotifyCredentials.accessToken, "Content-Type": "application/json" } | |
let npResult = await req.load() | |
if (req.response.statusCode == 401) { | |
// access token expired, trying to refresh | |
let success = await refreshSpotifyAccessToken() | |
if(success) { | |
return await loadNowPlaying() | |
} else { | |
return null | |
} | |
} else if (req.response.statusCode == 204) { | |
// no playback | |
return null | |
} else if (req.response.statusCode == 200) { | |
npResult = JSON.parse(npResult.toRawString()) | |
} | |
return npResult | |
} | |
// load and validate spotify credentials from iCloud Drive | |
async function loadSpotifyCredentials() { | |
let fm = FileManager.iCloud() | |
let dir = fm.documentsDirectory() | |
let path = fm.joinPath(dir, "spotify-credentials.json") | |
let spotifyCredentials | |
if(fm.fileExists(path)) { | |
await fm.downloadFileFromiCloud(path) | |
let spotifyCredentialsFile = Data.fromFile(path) | |
spotifyCredentials = JSON.parse(spotifyCredentialsFile.toRawString()) | |
if (isNotEmpty(spotifyCredentials.clientId) | |
&& isNotEmpty(spotifyCredentials.clientSecret) | |
&& isNotEmpty(spotifyCredentials.accessToken) | |
&& isNotEmpty(spotifyCredentials.refreshToken)) { | |
return spotifyCredentials | |
} | |
} | |
return null | |
} | |
// helper function to check not empty strings | |
function isNotEmpty(stringToCheck) { | |
if (stringToCheck != null && stringToCheck.length > 0) { | |
return true | |
} else { | |
return false | |
} | |
} | |
// The Spotify access token expired so we get a new one by using the refresh token (Authorization Flow) | |
async function refreshSpotifyAccessToken() { | |
if(spotifyCredentials != null) { | |
let req = new Request("https://accounts.spotify.com/api/token") | |
req.method = "POST" | |
req.headers = { "Content-Type": "application/x-www-form-urlencoded" } | |
req.body = "grant_type=refresh_token&refresh_token=" + spotifyCredentials.refreshToken + "&client_id=" + spotifyCredentials.clientId + "&client_secret=" + spotifyCredentials.clientSecret | |
let result = await req.loadJSON() | |
spotifyCredentials.accessToken = result.access_token | |
let fm = FileManager.iCloud() | |
let dir = fm.documentsDirectory() | |
let path = fm.joinPath(dir, "spotify-credentials.json") | |
fm.write(path, Data.fromString(JSON.stringify(spotifyCredentials))) | |
return true | |
} | |
return false | |
} | |
// get images from local filestore or download them once | |
async function getImage(image) { | |
let fm = FileManager.local() | |
let dir = fm.documentsDirectory() | |
let path = fm.joinPath(dir, image) | |
if(fm.fileExists(path)) { | |
return fm.readImage(path) | |
} else { | |
// download once | |
let imageUrl | |
switch (image) { | |
case 'spotify-icon.png': | |
imageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Spotify_logo_without_text.svg/240px-Spotify_logo_without_text.svg.png" | |
break | |
case 'shuffle-icon.png': | |
imageUrl = "https://www.iconsdb.com/icons/download/white/shuffle-128.png" | |
break | |
case 'repeat-icon.png': | |
imageUrl = "https://www.iconsdb.com/icons/download/white/repeat-128.png" | |
break | |
case 'offline-icon.png': | |
imageUrl = "http://cdn.1001freedownloads.com/vector/thumb/98366/clarity-shutdown-icon.png" | |
break | |
default: | |
console.log(`Sorry, couldn't find ${image}.`); | |
} | |
let iconImage = await loadImage(imageUrl) | |
fm.writeImage(path, iconImage) | |
return iconImage | |
} | |
} | |
// helper function to download an image from a given url | |
async function loadImage(imgUrl) { | |
const req = new Request(imgUrl) | |
return await req.loadImage() | |
} |
same problem what do I do?
See what I wrote under „Optionally“:
https://gist.github.com/marco79cgn/79a6a265d978dc22cc2a12058b24e02b#gistcomment-3470482
Hi. I did everything on the tutorial and I got this. I got my credentials into the shortcut and added it to the right folder. I have the beta version of the app running too but I just can’t figure out what to do.
There seems to be a problem with your Spotify credentials. Have a look in your „Files“ app and check the content of /Scriptable/spotify-credentials.json. It should contain four values (as described in my first comment).
Try deleting the widget from your homescreen, run the shortcut again and then set up the widget again. Make sure that Spotify is playing music before setting up the widget. Long press the widget, edit its settings and choose „when interacting: Run script“. Also insert a random widget parameter.
Hey, thank you for the great job on this script. Just wondering if there’s away to have the Spotify, shuffle and repeat icons along the bottom instead of the right side?
Many thanks.
See what I wrote under „Optionally“:
https://gist.github.com/marco79cgn/79a6a265d978dc22cc2a12058b24e02b#gistcomment-3470482
got it! but look at the size of the spotify icons
Sry for the dumb question, but...
Where would I have to edit the Script, so when i tap it, it would run the script and doesn't open spotify?
Hey Marco. I love the widget, works perfectly. Thanks a ton! What I'd like to do however when there's no track playing I'd rather want to have recently played album art displayed. Should be possible with the Spotify API. Have you thought about recently played at all?
Hey Marco. I love the widget, works perfectly. Thanks a ton! What I'd like to do however when there's no track playing I'd rather want to have recently played album art displayed. Should be possible with the Spotify API. Have you thought about recently played at all?
That's a good idea, haven't thought about it yet. How would you propose to display it? In the same (small) widget size with one track at a time and then shuffling over the history?
Well I guess you could of course also make a larger widget (similar to the official Spotify widget) and then display last x tracks. I'm rather looking for the small widget and would probably just look for the latest one of recently played (limit=1). I'm also displaying the album cover only and in full on the widget. That way I'm getting - what I believe is a very nice design on the homescreen - but also some functionality (as we can open the Spotify App when clicking on the Widget)
This could be achieved way easier by just caching the last played song and playing it until a new one is returned.
Ha. Totally right. Didn’t think of that.
Larger widget with some recent tracks would steel be nice though.
Really cool. is the testflight beta version still needed?
Really cool. is the testflight beta version still needed?
No, it‘s not. It works with the latest official AppStore version. I‘ll change the description.
When I do everything, then put the widget on my home screen, the widget says “Error: cannot parse response to an image”
I have a couple of weeks modifying your code, to create my own version of the widget, but I would like to select 'play',pause','next','previous' as buttons with functions in the widget, only I don't know how to add them. I already add a function(play), but I don't know how to add more than one.
I'll attach more screenshots and a video later so you can see the problem and if you like I can send you the code directly to see if you can make the addition of these.
By the way, I hope it wasn't wrong for me to take your code without asking to modify it.
Excuse my English, I speak Spanish, but I understand English a little bit.
This worked for me before, but now when the shortcut attempts to get authorization, the web page shows INVALID_CLIENT: Invalid redirect URI
, and the shortcut returns Invalid grant. Your code is probably expired! Please try again.
I've tried deleting both the script and the shortcut, but the problem persists.
Edit: Never mind. I must have edited the redirect URI in the app I created in Spotify.
This worked for me before, but now when the shortcut attempts to get authorization, the web page shows
INVALID_CLIENT: Invalid redirect URI
, and the shortcut returnsInvalid grant. Your code is probably expired! Please try again.
I've tried deleting both the script and the shortcut, but the problem persists.
Edit: Never mind. I must have edited the redirect URI in the app I created in Spotify.
Please go to your Spotify developer dashboard, choose your client, edit settings and enter https://example.com/callback as redirect URI. Save it. It should work afterwards.
Thanks, yes, that was it.
Thanks for the cool widget!
How do change the background to transparent?
Hey get the message that it can’t find my credentials? Also when I run the Spotify with workflow it says my client I’d and secret are invalid
I have a couple of weeks modifying your code, to create my own version of the widget, but I would like to select 'play',pause','next','previous' as buttons with functions in the widget, only I don't know how to add them. I already add a function(play), but I don't know how to add more than one.
I'll attach more screenshots and a video later so you can see the problem and if you like I can send you the code directly to see if you can make the addition of these.By the way, I hope it wasn't wrong for me to take your code without asking to modify it.
Excuse my English, I speak Spanish, but I understand English a little bit.
Hey. Does your widget "update" work as you planned it? I am really interested in a version giving me the possibility to play/pause (etc.). Thanks!
I have a couple of weeks modifying your code, to create my own version of the widget, but I would like to select 'play',pause','next','previous' as buttons with functions in the widget, only I don't know how to add them. I already add a function(play), but I don't know how to add more than one.
I'll attach more screenshots and a video later so you can see the problem and if you like I can send you the code directly to see if you can make the addition of these.By the way, I hope it wasn't wrong for me to take your code without asking to modify it.
Excuse my English, I speak Spanish, but I understand English a little bit.
How do you start the function „play“?
So how do you add a button to a widget?
Could you share your code so I can analyse it to maybe help?
I would like to see the next/ back button and play/pause button on the small widget! Is this possible?
See what I wrote under „Optionally“:
https://gist.github.com/marco79cgn/79a6a265d978dc22cc2a12058b24e02b#gistcomment-3470482got it! but look at the size of the spotify icons
I have tested all the solutions according to the author's reply to you, but it still doesn't work, how did you solve it.
I have a couple of weeks modifying your code, to create my own version of the widget, but I would like to select 'play',pause','next','previous' as buttons with functions in the widget, only I don't know how to add them. I already add a function(play), but I don't know how to add more than one.
I'll attach more screenshots and a video later so you can see the problem and if you like I can send you the code directly to see if you can make the addition of these.
By the way, I hope it wasn't wrong for me to take your code without asking to modify it.
Excuse my English, I speak Spanish, but I understand English a little bit.How do you start the function „play“? So how do you add a button to a widget? Could you share your code so I can analyse it to maybe help?
Can you share that widget with the play and pause buttons?
Sorry to bother if you don't have these widgets now, but if you do can you share them?
Thanks in advance!
Hi. I did everything on the tutorial and I got this. I got my credentials into the shortcut and added it to the right folder. I have the beta version of the app running too but I just can’t figure out what to do.