Skip to content

Instantly share code, notes, and snippets.

@marco79cgn
Last active October 29, 2024 11:18
Show Gist options
  • Save marco79cgn/79a6a265d978dc22cc2a12058b24e02b to your computer and use it in GitHub Desktop.
Save marco79cgn/79a6a265d978dc22cc2a12058b24e02b to your computer and use it in GitHub Desktop.
A Scriptable iOS widget that shows what‘s playing on Spotify
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()
}
@deroverda
Copy link

Really cool. is the testflight beta version still needed?

@marco79cgn
Copy link
Author

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.

@Narwhaluto2
Copy link

When I do everything, then put the widget on my home screen, the widget says “Error: cannot parse response to an image”

@Asrrath
Copy link

Asrrath commented Dec 3, 2020

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.

IMG_0709
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.

@paulrudy
Copy link

paulrudy commented Dec 7, 2020

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.

@marco79cgn
Copy link
Author

marco79cgn commented Dec 7, 2020

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.

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.

@paulrudy
Copy link

paulrudy commented Dec 8, 2020

Thanks, yes, that was it.

@ellllie
Copy link

ellllie commented Dec 17, 2020

Thanks for the cool widget!
How do change the background to transparent?

@bcronk12
Copy link

bcronk12 commented Dec 31, 2020

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

@bford19632198
Copy link

I have a problem I copied the code and put it into scriptable and I get this message...
D34B8D6B-3277-409F-A504-595B6D0E219A
How do I fix it ?

@hewaaa89
Copy link

hewaaa89 commented Jan 6, 2021

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.

IMG_0709
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!

@29SimonB
Copy link

29SimonB commented Jan 11, 2021

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.

IMG_0709
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?

@bcronk12
Copy link

bcronk12 commented Jan 11, 2021

I would like to see the next/ back button and play/pause button on the small widget! Is this possible?

@a672965727
Copy link

A2DAAEDC-F9FA-48BD-AC68-A664F6ED2FBD
same problem what do I do?

See what I wrote under „Optionally“:
https://gist.github.com/marco79cgn/79a6a265d978dc22cc2a12058b24e02b#gistcomment-3470482

E8D0E592-9036-4200-9D43-3ADAEC2CE0E3

got 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.

@Asrrath
Copy link

Asrrath commented Jul 13, 2021

I still can't get it to update fast, but through the spotify API I made Play / Pause / Next / Previous work.
7B8F2F4B-C093-44CC-BF87-557A1CBE8561

@Asrrath
Copy link

Asrrath commented Jul 13, 2021

@Hucksleyy91
Copy link

Hucksleyy91 commented Dec 18, 2021

I keep getting this #
E01C762B-0C58-469B-88EC-21E63C5550DF
I’ve done everything correct just made the DEV account too

@itaysharir
Copy link

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.
IMG_0709
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!

@rizwaneramadan
Copy link

rizwaneramadan commented Nov 4, 2023

IMG_9220

Whoever made it, I am very grateful for making the music playback widget from Spotify

But this only has a little problem with the playback being slow to update so there is a delay when the next new song is played. This widget doesn't immediately change it to the song that is currently playing but was delayed for some time, and it changes by itself. Hopefully it will be improved in the future. once again I am very grateful.

Why doesn't Spotify directly have a playback widget, only a playlist widget? I don't know, maybe there will be one later, adios thank you.

@marco79cgn
Copy link
Author

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.

@Firepixel85
Copy link

Please help the shortcut is telling me that it got an invalid grant. I troubleshooted thus far, but now I’m stuck.

@Firepixel85
Copy link

Firepixel85 commented Jun 29, 2024

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:
IMG_0909
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!

@gplbase
Copy link

gplbase commented Aug 19, 2024

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

@Firepixel85
Copy link

@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.

@Chamin462
Copy link

Chamin462 commented Sep 30, 2024

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment