Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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()
}
@marco79cgn

This comment has been minimized.

Copy link
Owner Author

@marco79cgn marco79cgn commented Sep 28, 2020

Intro

iOS 14 Custom Widget for the Scriptable app. It shows what‘s currently being played on Spotify with the help of the official Spotify Web API. Upon tapping on the widget it opens the Spotify app.

Requirements

  • iOS 14
  • Scriptable version 1.6 (or higher)
  • Spotify Web Developer API
    Please create a client to get your client id and client_secret credentials. They are needed for the widget. Furthermore you have to grant permissions to the client to read your Spotify‘s playback state. This has to be done only once and involves a Browser/Safari. I prepared a Siri Shortcut which simplifies the whole process of the Authorization Code Flow.

How to use the Siri shortcut:

  1. Download and install it
  2. Provide your Spotify client id and client_secret to the import wizard
  3. Run the shortcut
  4. a Spotify web page will be opened asking you to grant the needed permissions, click „Agree“ at the bottom
  5. you will be redirected to a example.com webpage - don‘t close it yet
  6. click on the sharing button at the bottom of the embedded Safari window and choose „copy“
  7. now close the browser window by pushing „Done“ on the upper left
  8. when asked for a folder to save the credentials file, choose the Scriptable folder in your iCloud Drive

That‘s it. At the end there will be a file named spotify-credentials.json inside your Scriptable folder in iCloud Drive with the following content:
{ "clientSecret":"xxx", "clientId":"xxx", "accessToken":"xxx", "refreshToken":"xxx" }
Make sure to keep this sensitive information safe and don‘t share it anywhere.

Background: The access token is needed by the widget to get your current playback information. The token expires after one hour but the widget is able to detect an expired token and will automatically refresh the token (with the help of the refresh token, client id and secret).

Caveat

Currently there‘s no chance to force an update of the widget‘s content. Only iOS decides when exactly widgets are updated. It‘s approximately every 5 minutes but it depends on different factors (like the battery state, low power mode, etc.). As a consequence, the content of the widget might be outdated or lagging behind. Until this update behavior changes, the widget is a proof of concept.

Thanks

A big Thank you to @simonbs for making great apps like Scriptable, DataJar or Jayson.

@CarePackageUK

This comment has been minimized.

Copy link

@CarePackageUK CarePackageUK commented Sep 28, 2020

Hi there, thank you for sharing your code, i have a little problem.
I copied your code from above and placed it in scriptable and followed the instructions, but my widget shows this message. Check picture.
Any help would be great.
Thank you.
505946BE-BCE8-4F16-9F53-F4546C9C4DE7

@marco79cgn

This comment has been minimized.

Copy link
Owner Author

@marco79cgn marco79cgn commented Sep 28, 2020

Try to force an update by editing the Scriptable widget details. Choose „when interacting: Run script“ and insert a random widget parameter.
I designed the layout for the small widget.

@iibraxton

This comment has been minimized.

Copy link

@iibraxton iibraxton commented Sep 28, 2020

I followed everything and it showed the same problem as carepackageuk, but now it shows error code message
20A9546A-1C33-4EBC-A73B-2D6898F2A7BF

@marco79cgn

This comment has been minimized.

Copy link
Owner Author

@marco79cgn marco79cgn commented Sep 28, 2020

You have to use the latest Testflight Beta version. It doesn‘t work yet with the Scriptable AppStore version as it doesn't support stacks yet. See the requirements. ;)

@elllebsack

This comment has been minimized.

Copy link

@elllebsack elllebsack commented Sep 28, 2020

I have completed all the steps, how do I get it onto my home screen? When I add a scriptable widget and click the correct code, nothing happens.

@marco79cgn

This comment has been minimized.

Copy link
Owner Author

@marco79cgn marco79cgn commented Sep 28, 2020

  1. make sure to have the latest Testflight beta version of scriptable https://testflight.apple.com/join/uN1vTqxk

  2. copy the code from above (ideally by pushing the „Raw“ button before)

  3. open Scriptable, push the „+“ button, give it a name, paste the code and save it

  4. make sure Spotify is playing a song (so you don't have to wait for the next refresh of the widget)

  5. on your homescreen, long press anywhere to make it jiggle, push the „+“ button on the upper left and choose Scriptable → small widget

  6. long press on the empty widget, assign it the created script (from step 3), choose „Run script“ as an option for „when interacting“ and then close it

Optionally: if you've done all that before but nothing was playing on your Spotify app, edit the widget settings again and insert a random widget parameter string. This should force the widget content to update. Another option would be to run the script inside the Scriptable app.

Hope that helps.

@elllebsack

This comment has been minimized.

Copy link

@elllebsack elllebsack commented Sep 28, 2020

I got it to work!! Just wanted to say thank you, I have no idea how coding or anything of this confusing stuff works, but your instructions helped a lot and it’s definitely the coolest thing on my phone. Gonna impress some friends

@ollywoollatt

This comment has been minimized.

Copy link

@ollywoollatt ollywoollatt commented Sep 28, 2020

How do you change the colour of the shuffle and repeat button to black

@ollywoollatt

This comment has been minimized.

Copy link

@ollywoollatt ollywoollatt commented Sep 28, 2020

How do you get the artwork and song to refresh quicker

@marco79cgn

This comment has been minimized.

Copy link
Owner Author

@marco79cgn marco79cgn commented Sep 29, 2020

How do you get the artwork and song to refresh quicker

Unfortunately you can‘t. See what I wrote above (Caveat).

@vntrembala

This comment has been minimized.

Copy link

@vntrembala vntrembala commented Oct 3, 2020

A2DAAEDC-F9FA-48BD-AC68-A664F6ED2FBD

same problem what do I do?

@misskani

This comment has been minimized.

Copy link

@misskani misskani commented Oct 3, 2020

0CB52C68-5446-4A0B-AD64-57691F9FEC2B

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.

@marco79cgn

This comment has been minimized.

Copy link
Owner Author

@marco79cgn marco79cgn commented Oct 3, 2020

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

@marco79cgn

This comment has been minimized.

Copy link
Owner Author

@marco79cgn marco79cgn commented Oct 3, 2020

0CB52C68-5446-4A0B-AD64-57691F9FEC2B

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.

@CarePackageUK

This comment has been minimized.

Copy link

@CarePackageUK CarePackageUK commented Oct 3, 2020

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.

@vntrembala

This comment has been minimized.

Copy link

@vntrembala vntrembala commented Oct 3, 2020

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

@jakobtrk

This comment has been minimized.

Copy link

@jakobtrk jakobtrk commented Oct 8, 2020

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?

@therau5sch

This comment has been minimized.

Copy link

@therau5sch therau5sch commented Oct 15, 2020

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?

@marco79cgn

This comment has been minimized.

Copy link
Owner Author

@marco79cgn marco79cgn commented Oct 15, 2020

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?

@therau5sch

This comment has been minimized.

Copy link

@therau5sch therau5sch commented Oct 15, 2020

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)

@marco79cgn

This comment has been minimized.

Copy link
Owner Author

@marco79cgn marco79cgn commented Oct 15, 2020

This could be achieved way easier by just caching the last played song and playing it until a new one is returned.

@therau5sch

This comment has been minimized.

Copy link

@therau5sch therau5sch commented Oct 15, 2020

Ha. Totally right. Didn’t think of that.
Larger widget with some recent tracks would steel be nice though.

@l2OTCEH

This comment has been minimized.

Copy link

@l2OTCEH l2OTCEH commented Oct 18, 2020

Running into an issue with code. States it’s probably expired. Any ideas?

768B35F5-9393-4B5E-B759-881FCEC2DC7A

@deroverda

This comment has been minimized.

Copy link

@deroverda deroverda commented Nov 17, 2020

Really cool. is the testflight beta version still needed?

@marco79cgn

This comment has been minimized.

Copy link
Owner Author

@marco79cgn marco79cgn commented Nov 17, 2020

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

This comment has been minimized.

Copy link

@Narwhaluto2 Narwhaluto2 commented Dec 1, 2020

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

@Asrrath

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link
Owner Author

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

This comment has been minimized.

Copy link

@paulrudy paulrudy commented Dec 8, 2020

Thanks, yes, that was it.

@ellllie

This comment has been minimized.

Copy link

@ellllie ellllie commented Dec 17, 2020

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

@bcronk12

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

@bford19632198 bford19632198 commented Jan 1, 2021

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

This comment has been minimized.

Copy link

@hewaaa89 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!

@SimonBoer

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

@a672965727 a672965727 commented Apr 15, 2021

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

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