-
-
Save marco79cgn/79a6a265d978dc22cc2a12058b24e02b to your computer and use it in GitHub Desktop.
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() | |
} |
Please help the shortcut is telling me that it got an invalid grant. I troubleshooted thus far, but now I’m stuck.
I Fixed it! For anyone getting the same error (invalid grant error), I made a new version of the shortcut: https://www.icloud.com/shortcuts/f4a511c04d9b4d51a087dfce91804bdf
Follow the same setup steps but after you run the shortcut and you are redirected to the example.com/callback page, press the little safari icon on the bottom right of your screen:
Then copy THE HOLE URL and paste it into the text field that will appear (you might need to to drag the text field with one hand to get it out of the way and copy the URL with the other). After that press done, save the file to the correct directory (ICloud Drive/Scriptable) and you are done! Hope I helped.
For anyone who need help or any other assistance related to the widget tag me and hopefully I will respond. Good luck on your endeavours!
@marco79cgn If you are not ok with me using your shortcut, just contact me to delete my post, or just delete it yourself. Cheers mate amazing work with the widget wish you the best!
I'm encountering an issue with the code I copied into Scriptable. I'm receiving the following error message...
I am trying to upload image directly but then searched on internet for the same issue and found the below screenshot from someone.
https://spotify-premium.app/wp-content/uploads/2024/08/error-spotify.png
@gplbase that's because you have not followed the steps to link your Spotify account . To fix that first go to the Spotify developer dashboard: https://developer.spotify.com/dashboard than create an app, set the name to whatever you want and set the redirect URL to example.com/callback and save it, then download the shortcut from the setup shortcut: https://www.icloud.com/shortcuts/f4a511c04d9b4d51a087dfce91804bdf and follow the setup steps, run it and after you do that and you are redirected to the example.com/callback page, press the little safari icon on the bottom right of your screen then copy THE HOLE URL and paste it into the text field that will appear (you might need to to drag the text field with one hand to get it out of the way and copy the URL with the other). After that press done, save the file to the correct directory (iCloud Drive/Scriptable) and you are done! Hope I helped.
Hey Marco,
I love the widget—it works perfectly. Thanks a ton! However, I'd like to make a suggestion. When no track is playing, I'd prefer to have recently played album art displayed instead. I believe this should be possible using the Spotify API. Have you considered incorporating a feature for recently played tracks?
By the way, I came across this site Spotich that might be helpful for further Spotify API integrations!
Feel free to discuss my widget or add ideas & improvements. But please stop spamming this thread with unofficial third party apps which are the least against the terms and conditions of Spotify and furthermore illegal in some countries. Thanks for your understanding.